Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .repository-projection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"projection": "deixic-code",
"projectionSchemaVersion": 1,
"sourceRepository": "dx-corp/mono",
"sourceSha": "e3696732f3a134d547cb39da1ae758c94843bc83",
"sourceSha": "5c0ef43f86d41311f868236ebbc050f14969e263",
"destinationRepository": "dx-corp/code",
"priorProjectedBase": "c0f08c593b1f58f7802b018315b296e2a1268912",
"priorProjectedBase": "931b3d60176d650c886e2ec3802031569808b470",
"definitionDigest": "82936441c776e3e8edb5d215a75007ec9714a233f489d460075d79d5ef5ba32f",
"toolDigest": "c244d99199a7ae3eb8ff644a99462163c23b0bb6a83ef50af01efbdca0b81d04",
"contentDigest": "bfa2803b378a3e7f65c5998f3de8f85c5490f0116c8c9a650fc9254777d3e49a",
"contentDigest": "11f79bfdda6fbb50a86620b4528c84bbabc195dc16b6c97058a812bcfadeb395",
"publicationEligible": true
}
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,46 @@ versioning when releases are cut.
and keep scheduled public runs inert so public publishing stays downstream of
the internal source-of-truth release.

## [0.10.111] - 2026-09-30

### Added

- Make dex-journey, one-command user-journey harness with latency table (#11440). <!-- maestro-release-note:ba20ac34c88f -->
- Serve Claude on Vertex AI as provider vertex-anthropic (#11427). <!-- maestro-release-note:c87946b49436 -->
- Run Claude on Vertex through the Messages loop with effort and prompt caching (#11424). <!-- maestro-release-note:7c90abda6731 -->
- Add protected bounded public conversation fuzz (#11417). <!-- maestro-release-note:7e35c8cf0f3a -->
- Dispatch a Dex-approved call without parking for its own resume (#11373). <!-- maestro-release-note:fc4185e21ce8 -->

### Changed

- Delete the test-only process runner and proactive investigation worker (#11437). <!-- maestro-release-note:1bfb914c8b70 -->
- Web e2e caller is a person; history stubs are not exposure (#11438). <!-- maestro-release-note:f4b5a528d48c -->
- Remove retired computer.coding_* capabilities and dead coding-workspace CI (#11442). <!-- maestro-release-note:81f9dafc53e6 -->
- Run desktop-lane main verdicts on arc-main-heavy (#11439). <!-- maestro-release-note:1eae29219ea2 -->
- Path-scope the Merlin, World and Computer iOS workflows at the trigger (#11435). <!-- maestro-release-note:ebe960117675 -->
- Make the parked-turn index probe discriminate (#11431). <!-- maestro-release-note:1908b4158ccb -->
- Repair solution provenance and vocabulary (#11428). <!-- maestro-release-note:194a452e3d87 -->
- Repair governed CRM solution metadata (#11416). <!-- maestro-release-note:08edb20b9fb1 -->
- Tighten protected fuzz cancellation and receipt proof (#11420). <!-- maestro-release-note:e8102707dbec -->
- Keep public mirror staging proof current (#11414). <!-- maestro-release-note:352bb9449360 -->
- Restore Postgres runtime fixture coverage (#11412). <!-- maestro-release-note:5b08baa67284 -->
- Exercise public web turn and receipt ownership in fuzz (#11409). <!-- maestro-release-note:dd8d7abb36ac -->

### Fixed

- Bound Rosetta release smoke with useful diagnostics. <!-- maestro-release-note:268ff18ab7d7 -->
- Keep each turn's context in its own user message for Claude (#11447). <!-- maestro-release-note:050dfb654467 -->
- Reach Claude on the Vertex us/eu multi-region endpoints (#11448). <!-- maestro-release-note:cb653a3dfc24 -->
- Declare the GetOperatingReceipt codec once (#11436). <!-- maestro-release-note:2dd6bcc04a02 -->
- Derive PartialEq for WorkloadProviderRef (#11441). <!-- maestro-release-note:6deac492a863 -->
- Keep Dex final visible until message hydration (#11434). <!-- maestro-release-note:b0a2afb4c8f3 -->
- Keep a cut-off answer instead of withdrawing it (#11433). <!-- maestro-release-note:ee9021b549fc -->
- Recall team memory once per turn, not once per model step (#11432). <!-- maestro-release-note:5eac38b192dc -->
- Resolve public owner receipt details before fuzz proof (#11426). <!-- maestro-release-note:ecaa036c2ed1 -->
- Forward long Dex answers over Watch (#11430). <!-- maestro-release-note:80cdbe25383f -->
- Read one-loop receipt details behind thread summaries (#11425). <!-- maestro-release-note:f61031ff6924 -->
- Bound provider streams by silence, not total length (#11429). <!-- maestro-release-note:9fc240ebab36 -->

## [0.10.110] - 2026-09-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@evalops/deixic-code",
"description": "Deixic Code — native Rust coding agent, CLI, TUI, and runtime gateway",
"version": "0.10.110",
"version": "0.10.111",
"private": false,
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion packages/maestro-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "maestro"
version = "0.10.110"
version = "0.10.111"
edition = "2021"
license = "MIT"
description = "Canonical native Rust CLI for Deixic Code"
Expand Down
22 changes: 7 additions & 15 deletions scripts/smoke-release-native-only.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { firstLaunchTimeoutMs, runNativeSmokeCommand } from "./smoke-release-process.mjs";

// The agent only serves protocol versions it implements, so the smoke has to
// announce the version this build speaks rather than a placeholder.
Expand Down Expand Up @@ -39,22 +40,13 @@ const env = {
TERM: "xterm-256color",
};

function run(args, input) {
const result = spawnSync(binary, args, {
encoding: "utf8",
env,
input,
timeout: 30_000,
});
if (result.status !== 0) {
throw new Error(`${args.join(" ")} failed:\n${result.stderr}\n${result.stdout}`);
}
return result.stdout;
}

if (!run(["--version"]).startsWith("deixic-code "))
if (!runNativeSmokeCommand(binary, ["--version"], {
env,
timeoutMs: firstLaunchTimeoutMs(process.platform, process.env.RELEASE_PLATFORM),
}).startsWith("deixic-code "))
throw new Error("version smoke failed");
if (!run(["--help"]).includes("Usage:")) throw new Error("help smoke failed");
if (!runNativeSmokeCommand(binary, ["--help"], { env }).includes("Usage:"))
throw new Error("help smoke failed");
const headlessInput = `${JSON.stringify({ type: "hello", protocol_version: protocolVersion, client_info: { name: "native-smoke", version: "1" }, role: "controller" })}\n${JSON.stringify({ type: "shutdown" })}\n`;
const headlessResult = spawnSync(binary, ["--headless"], {
encoding: "utf8",
Expand Down
30 changes: 30 additions & 0 deletions scripts/smoke-release-process.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { spawnSync } from "node:child_process";

const DEFAULT_TIMEOUT_MS = 30_000;

export function firstLaunchTimeoutMs(platform, releasePlatform) {
// Cold Rosetta translation of the signed Intel release can exceed the
// ordinary smoke budget. Keep the launch bounded and test every exit.
return platform === "darwin" && releasePlatform === "darwin-x64"
? 120_000
: DEFAULT_TIMEOUT_MS;
}

export function runNativeSmokeCommand(binary, args, { env, input, timeoutMs = DEFAULT_TIMEOUT_MS }) {
const result = spawnSync(binary, args, {
encoding: "utf8",
env,
input,
timeout: timeoutMs,
});
if (result.status !== 0) {
const details = [
`status=${result.status ?? "none"}`,
`signal=${result.signal ?? "none"}`,
`error=${result.error?.code ?? "none"}`,
`timeout=${timeoutMs}ms`,
].join(", ");
throw new Error(`${args.join(" ")} failed (${details}):\n${result.stderr ?? ""}\n${result.stdout ?? ""}`);
}
return result.stdout;
}
47 changes: 47 additions & 0 deletions scripts/smoke-release-process.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import assert from "node:assert/strict";
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { firstLaunchTimeoutMs, runNativeSmokeCommand } from "./smoke-release-process.mjs";

test("only Darwin x64 gets a longer first-launch smoke budget", () => {
assert.equal(firstLaunchTimeoutMs("darwin", "darwin-x64"), 120_000);
assert.equal(firstLaunchTimeoutMs("darwin", "darwin-arm64"), 30_000);
assert.equal(firstLaunchTimeoutMs("linux", "darwin-x64"), 30_000);
});

test("a timed-out native binary reports the timeout, signal, and spawn error", () => {
const directory = mkdtempSync(join(tmpdir(), "maestro-smoke-process-"));
try {
const binary = join(directory, "slow-binary");
writeFileSync(binary, "#!/bin/sh\n/bin/sleep 1\n");
chmodSync(binary, 0o755);
assert.throws(
() => runNativeSmokeCommand(binary, ["--version"], { env: process.env, timeoutMs: 20 }),
(error) => {
assert.match(error.message, /--version failed/);
assert.match(error.message, /error=ETIMEDOUT/);
assert.match(error.message, /timeout=20ms/);
return true;
},
);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});

test("a native binary with a successful exit returns its output", () => {
const directory = mkdtempSync(join(tmpdir(), "maestro-smoke-process-"));
try {
const binary = join(directory, "version-binary");
writeFileSync(binary, "#!/bin/sh\nprintf 'deixic-code 0.10.110\\n'\n");
chmodSync(binary, 0o755);
assert.equal(
runNativeSmokeCommand(binary, ["--version"], { env: process.env }),
"deixic-code 0.10.110\n",
);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
24 changes: 24 additions & 0 deletions vendor/dex-loop/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ pub struct Context {
/// `Final`) are never attributed to the turn that raced in ahead of them.
pending_turns: Vec<PendingTurn>,
authorized_principal: Option<PrincipalId>,
/// Unknown call outcomes in this turn, derived from the durable log.
/// Kept outside model history so compaction cannot permit a fresh retry.
uncertain_calls: Vec<ProposedCall>,
}

#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -184,6 +187,7 @@ impl Context {
authorized_tools: Vec::new(),
approval_mode: ApprovalMode::Interactive,
authorized_principal: None,
uncertain_calls: Vec::new(),
pending_turns: Vec::new(),
}
}
Expand Down Expand Up @@ -302,6 +306,16 @@ impl Context {
self.open_step.as_ref()
}

/// A fresh ID does not make an unresolved operation safe to repeat.
pub(crate) fn has_uncertain_call(&self, call: &ProposedCall) -> bool {
self.uncertain_calls.iter().any(|prior| {
prior.id != call.id
&& prior.tool == call.tool
&& prior.args_digest == call.args_digest
&& prior.principal == call.principal
})
}

/// The step of a model attempt that started but never completed.
pub(crate) fn open_attempt(&self) -> Option<u32> {
self.attempt
Expand Down Expand Up @@ -470,6 +484,15 @@ impl Context {
output,
receipt,
} => {
self.uncertain_calls.retain(|prior| &prior.id != call);
if *outcome == Outcome::Unknown
&& let Some(proposal) = self
.open_step
.as_ref()
.and_then(|step| step.calls.iter().find(|proposal| &proposal.id == call))
{
self.uncertain_calls.push(proposal.clone());
}
if let Some(state) = self.state_mut(call) {
*state = CallState::Done(ToolResult {
outcome: *outcome,
Expand Down Expand Up @@ -564,6 +587,7 @@ impl Context {
self.attempt = None;
self.interrupt_requested = false;
self.exposed.clear();
self.uncertain_calls.clear();
self.client_tools = client_tools;
self.authorized_tools = authorized_tools;
self.approval_mode = approval_mode;
Expand Down
54 changes: 53 additions & 1 deletion vendor/dex-loop/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const UNKNOWN_INTERRUPTED: &str =
/// a `Running` outcome that nothing will update: both invite the model to
/// retry, which mints a new `CallId` and can run the mutation twice.
const UNKNOWN_NO_RETRY: &str = "outcome unknown: this call already started; do not retry it without first checking whether it took effect";
const UNCERTAIN_REPEAT: &str = "not run: the same operation has an unknown outcome in this turn; check whether it took effect before trying again";
const APPROVER_DECLINED: &str = "denied: the approver declined this call";
const APPROVAL_MISMATCH: &str = "denied: the approval does not match this call's arguments";
const MISSING_QUESTION: &str = "invalid call: args.question must be a non-empty string";
Expand Down Expand Up @@ -382,6 +383,30 @@ where
self.emit(ctx, events).await?;
return Ok(Some(Exit::Failed));
}
if failure.is_some() && !text.is_empty() && !cancel.is_cancelled() {
// The customer already read this text. Keep it as the
// answer, visibly marked as cut off, instead of withdrawing
// it: a long answer that loses its stream near the end (a
// provider or gateway limit) must not vanish.
let mut text = text;
let tail = filter.finish();
if !tail.is_empty() {
text.push_str(&tail);
self.log.append_text(tail).await?;
}
self.log.append_text(CUT_OFF_NOTICE.to_owned()).await?;
text.push_str(CUT_OFF_NOTICE);
let mut events = pending_usage;
events.push(Event::ModelStepCompleted {
step,
text: text.clone(),
calls: Vec::new(),
reasoning: None,
});
events.push(Event::Final { text });
self.emit(ctx, events).await?;
return Ok(Some(Exit::Done));
}
if let Some(message) = failure {
let mut events = pending_usage;
events.push(Event::ModelAttemptAbandoned { step });
Expand Down Expand Up @@ -613,6 +638,9 @@ where
continue;
}
}
if self.refuse_uncertain_repeat(ctx, call, &spec).await? {
continue;
}
if let (None, Verdict::NeedsApproval { approval, summary }) = (decision, verdict) {
if self
.flush(ctx, &calls, &mut wave, cancel, run_started)
Expand Down Expand Up @@ -670,6 +698,22 @@ where
Ok(None)
}

/// Refusal does not claim or dispatch a second effect. Reads remain safe
/// to retry; an existing call ID still resolves through its effect ledger.
async fn refuse_uncertain_repeat(
&self,
ctx: &mut Context,
call: &ProposedCall,
spec: &ToolSpec,
) -> Result<bool, Fenced> {
if spec.read_only || !ctx.has_uncertain_call(call) {
return Ok(false);
}
self.finish(ctx, call, ToolResult::error(UNCERTAIN_REPEAT))
.await?;
Ok(true)
}

/// One call to a `Client`-executor tool: approval (if the host's
/// allowlist marked it a mutation), then `ClientToolRequested`, mirroring
/// how the main `dispatch` loop handles `NeedsApproval` and `User`.
Expand Down Expand Up @@ -701,7 +745,11 @@ where
.await?;
return Ok(ClientToolOutcome::Continue);
}
} else if spec.governance == GovernanceClass::Approval {
}
if self.refuse_uncertain_repeat(ctx, call, &spec).await? {
return Ok(ClientToolOutcome::Continue);
}
if decision.is_none() && spec.governance == GovernanceClass::Approval {
if self.flush(ctx, calls, wave, cancel, run_started).await? {
return Ok(ClientToolOutcome::Break);
}
Expand Down Expand Up @@ -1150,6 +1198,10 @@ fn search_spec() -> ToolSpec {
}
}

/// Appended to an answer whose model stream failed after text was shown.
pub const CUT_OFF_NOTICE: &str =
"\n\n_This answer was cut off before it finished. Ask me to continue from here._";

fn call_id(turn: &TurnId, step: u32, index: usize) -> CallId {
CallId(format!("{turn}-{step}-{index}"))
}
Expand Down
2 changes: 1 addition & 1 deletion vendor/dex-loop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ mod sanitize;
pub use budget::{Budget, BudgetAxis};
pub use compaction::{Compaction, Compactor, NoCompaction, Summarize, Threshold};
pub use context::{Context, Entry, Message};
pub use engine::{DEFAULT_TOOL_CALL_DEADLINE, Engine, Exit, TOOLS_SEARCH};
pub use engine::{CUT_OFF_NOTICE, DEFAULT_TOOL_CALL_DEADLINE, Engine, Exit, TOOLS_SEARCH};
pub use event::{
ApprovalId, ApprovalMode, ArtifactRef, CallId, ClientToolSpec, Cursor, ErrorCode, Event,
HEADLESS_AUTO_APPROVER, MessageId, Outcome, Output, OutputRef, PrincipalId, ProposedCall,
Expand Down
Loading
Loading