@deksden-com/dd-flow-cli 0.9.0-beta.91 → 0.9.0-beta.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.93
4
+
5
+ ### Patch Changes
6
+
7
+ - Move native ZCode request usage accounting into dd-zcode, preserve causal native errors, and distinguish bridge contract support from exact-tuple runtime qualification.
8
+
9
+ ## 0.9.0-beta.92
10
+
11
+ ### Patch Changes
12
+
13
+ - 03fea2a: Accept the behaviorally qualified ZCode 0.16.9 and zcode-acp 0.43.2 harness overlay tuple in the managed runtime gate.
14
+
3
15
  ## 0.9.0-beta.91
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # dd-flow-cli
2
2
 
3
+ See [dd-zcode integration](src/harness-runtime/DD-ZCODE.md) for native transport,
4
+ adapter ownership and the versioned bridge contract.
5
+
3
6
  ## Managed temporary HTTP services
4
7
 
5
8
  For an interactive scenario, `runtime process start --run <RUN-ID>
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.91",
4
- "cli_commit": "82e57ba33fbc4e915c2b5809dd82fe9033969c3a",
5
- "built_at": "2026-09-21T18:07:32.802Z",
3
+ "cli_version": "0.9.0-beta.93",
4
+ "cli_commit": "4af4acb64cbb4a27697a9de92a74ab6848745b17",
5
+ "built_at": "2026-09-22T05:53:32.684Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "d1a6081ab15ab92ac917ff5d037121a40c709db1",
@@ -0,0 +1,84 @@
1
+ # dd-zcode integration boundary
2
+
3
+ `bin/dd-zcode.mjs`, `lib/dd-zcode.mjs` and `lib/dd-zcode-daemon.mjs` implement the
4
+ dd-flow adapter over ACP. The maintained bridge is
5
+ [deksden-com/zcode-acp](https://github.com/deksden-com/zcode-acp), based on upstream
6
+ [william0wang/zcode-acp](https://github.com/william0wang/zcode-acp).
7
+
8
+ | Owner | Responsibility |
9
+ | --- | --- |
10
+ | Upstream bridge | ZCode protocol compatibility, ACP transport, native event translation and general session mechanics. |
11
+ | Downstream bridge extensions | Expose native identity, evidence and control primitives missing upstream; preserve native errors and side-effect semantics. |
12
+ | dd-zcode | Requested/observed profile checks, native-to-dd-flow identity mapping, event normalization, lifecycle forwarding, execution daemon ownership, cancellation/settlement verification and usage forwarding. |
13
+ | dd-flow services | Durable lifecycle decisions, Work/RUN state and usage ingestion/accounting. |
14
+ | dd-eval | Qualify the complete tuple, pin profiles/artifacts and run/monitor evaluations. |
15
+
16
+ Native turn completion is evidence, not proof of semantic Work success. A
17
+ closed transport is not proof that its native descendants stopped. The adapter
18
+ must retain causal errors, missing observations and usage completeness rather
19
+ than turn them into successful/zero-valued results.
20
+
21
+ ## Bridge contract
22
+
23
+ The identity check uses `--dd-harness-version` (`dd-zcode-harness@1` for existing
24
+ qualified builds, `dd-zcode-harness@2` for the native-usage candidate) and
25
+ `--dd-harness-commit`, alongside native and bridge versions. This is the current
26
+ mechanism, not a general capability negotiation protocol. `zcodeLifecycleQualification`
27
+ also admits exact native-binary SHA / bridge-commit tuples; a new tuple requires
28
+ behavioral evidence before admission. Merely changing an allowlist is not that
29
+ evidence.
30
+
31
+ The adapter consumes `zcode/session/resolve`, `read`, `subagents`, `usage`,
32
+ `close`, `resident` and `retainedSubagents`, plus standard/existing session
33
+ operations. Resolution must preserve the session's trusted workspace.
34
+ Residency and retained-tree reads after close must not implicitly resume it.
35
+ Cancellation must target owned sessions and verify resulting tree state.
36
+ Profile facts must come from native readback. Session updates supply tool events;
37
+ the adapter forwards them to `dd-flow zcode event handle`, where trusted
38
+ invocation/lifecycle receipts are enforced.
39
+
40
+ The execution-scoped daemon preserves the live native handles needed for
41
+ background children. Inspection remains available during productive operations;
42
+ a lost owner cannot be repaired by pretending retained topology is a live tree.
43
+ Keep process identity, native identity and adapter control locator distinct.
44
+
45
+ ## Usage and contract @2
46
+
47
+ The @2 bridge returns native usage unchanged. `lib/zcode-usage.mjs` reads native
48
+ session evidence and computes request counters in the adapter. It deduplicates
49
+ identified requests, rejects conflicting/invalid measurements and records read
50
+ failures as incomplete evidence. Full history is required for cumulative usage;
51
+ turn-liveness history limits must not be applied here. Immutable @1 bridges
52
+ remain supported through their existing measured projection. All adapter usage
53
+ paths use the same helper; dd-flow services still own accounting deltas.
54
+
55
+ Contract support does not admit a new binary/commit tuple automatically. The
56
+ v0.46.7 @2 candidate is not added to production qualification without live
57
+ receipts. Preserve that fail-closed distinction when preparing the next eval.
58
+
59
+ Future consumer-only retry, recovery, readiness and accounting rules belong
60
+ here or in the owning dd-flow service. Bridge changes are justified when native
61
+ access or protocol mechanics cannot be implemented through its public interface.
62
+ Review existing patches individually; do not claim that all policy has already
63
+ been extracted from the bridge.
64
+
65
+ ## Verification and operations
66
+
67
+ Relevant adapter regressions include `test/zcode-invocation-observer.test.ts`;
68
+ select the additional daemon, usage and lifecycle tests affected by a change.
69
+ Bridge tests check transport facts; adapter tests check their interpretation;
70
+ live qualification checks the actual installed tuple. Release the adapter with
71
+ dd-flow when adapter code or qualification admission changes. A bridge-only
72
+ update does not inherently require an adapter release, but current exact-commit
73
+ admission may require one.
74
+
75
+ Follow the [ZCode upgrade runbook](https://github.com/deksden-com/dd-eval/blob/main/runbooks/update-zcode.md)
76
+ for release discovery, qualification, rollout and rollback. Fork patch maintenance
77
+ is documented in the fork itself. Neither procedure is duplicated here.
78
+
79
+ Adapter changes use short-lived `feat/`, `fix/` or `docs/` branches from `main`,
80
+ reviewed with affected checks before integration (normally squash merge).
81
+ Releases identify verified immutable artifacts, not moving main HEAD. See the
82
+ [cross-repository Git workflow](https://github.com/deksden-com/dd-eval/blob/main/runbooks/git-workflow.md)
83
+ for upgrade ordering, candidate acceptance and hotfixes. Branch-protection
84
+ configuration is separate from this documented policy.
@@ -9,7 +9,9 @@ import readline from "node:readline";
9
9
  import { stopProcessGroup } from "./managed-daemon.mjs";
10
10
  import { checkObservedProfile, observeModel, observedFields } from "./model-observations.mjs";
11
11
 
12
+ import { readZcodeUsage } from "./zcode-usage.mjs";
12
13
  const ZCODE_HARNESS_CONTRACT = "dd-zcode-harness@1";
14
+ export const supportedZcodeHarnessContract = contract => [ZCODE_HARNESS_CONTRACT, "dd-zcode-harness@2"].includes(contract);
13
15
  // A deadline is an observation boundary, not permission to replay an ACP
14
16
  // request. Keep the original correlation slot briefly so a slow native
15
17
  // response (especially cold-start/session creation) can still settle it.
@@ -263,7 +265,7 @@ export class AcpBridge {
263
265
  const providerDetails = provider?.message ?? null;
264
266
  const messageText = [message.error.message, details, providerDetails].filter(Boolean).map(value => typeof value === "string" ? value : JSON.stringify(value)).join(": ") || JSON.stringify(message.error);
265
267
  const error = new Error(messageText);
266
- const nativeCode = typeof data?.code === "string" ? data.code : null;
268
+ const nativeCode = typeof data?.code === "string" ? data.code : typeof data?.native_code === "string" ? data.native_code : null;
267
269
  const unknown = ["native_timeout", "native_backend_dead", "native_backend_pipe_broken", "native_outcome_unknown"].includes(nativeCode);
268
270
  error.code = unknown ? nativeCode : /rate limit/i.test(messageText) ? "provider_rate_limited" : /payment required|usage balance exhausted/i.test(messageText) ? "provider_quota_exhausted" : "acp_request_failed";
269
271
  error.retryable = !unknown && /rate limit/i.test(messageText);
@@ -459,7 +461,7 @@ async function inspect(bridge, sessionId) {
459
461
  bridge.request("zcode/session/subagents", { sessionId }),
460
462
  ]);
461
463
  let usage = null, usage_observation_error = null;
462
- try { usage = await bridge.request("zcode/session/usage", { sessionId }); }
464
+ try { usage = await readZcodeUsage(bridge, sessionId); }
463
465
  catch (error) { usage_observation_error = { code: error.code ?? "usage_observation_failed", message: error.message ?? String(error) }; }
464
466
  const observed = observedProfile(read);
465
467
  await bridge.recordModel?.(sessionId, observed, "zcode/session/read");
@@ -753,8 +755,12 @@ export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContrac
753
755
  // Behavioural evidence: dd-eval production-invocations probe, 2026-09-13.
754
756
  // Pin both providers of native event identity, not a string in the bundle.
755
757
  // 60af0d3 adds retainedSubagents without changing native event forwarding.
756
- const qualified = sha256 === "e9f1868c0fdb863537ed910ee3828b9be96b8c2fd805473f63b439e1113266b8"
757
- && ["e0600fe3c46e215695f257e65aa6f674ced998d8", "60af0d31e13076a313d9770f10aa70f7c94742cf"].includes(bridgeCommit) && harnessContract === ZCODE_HARNESS_CONTRACT;
758
+ const qualifiedTuples = new Map([
759
+ ["e9f1868c0fdb863537ed910ee3828b9be96b8c2fd805473f63b439e1113266b8", new Set(["e0600fe3c46e215695f257e65aa6f674ced998d8", "60af0d31e13076a313d9770f10aa70f7c94742cf"])],
760
+ ["500ae84fa2cb8dd74c1264e5f3d3709c004c59b58899f1b0c3e205137a0ef466", new Set(["8c0a893f3c26a0b96cb138acf762db84c800298b"])],
761
+ ]);
762
+ const qualified = qualifiedTuples.get(sha256)?.has(bridgeCommit) === true
763
+ && harnessContract === ZCODE_HARNESS_CONTRACT;
758
764
  return { status: qualified ? "qualified" : "unqualified", mechanism: "cli-invocation@1", sha256,
759
765
  capabilities: qualified ? ["root", "concurrent_children", "child_continuation"] : [],
760
766
  native_nested_children: false, reason: qualified ? null : "native_event_contract_unqualified" };
@@ -764,12 +770,12 @@ export async function doctor(options = {}) {
764
770
  const zcodeAcp = await commandOutput(options.bin, ["--version"]);
765
771
  const zcodeAcpCommit = await commandOutput(options.bin, ["--dd-harness-commit"]);
766
772
  const ddHarness = await commandOutput(options.bin, ["--dd-harness-version"]);
767
- const zcodePath = options.zcodePath ?? process.env.ZCODE_PATH ?? "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs";
773
+ const zcodePath = options.zcodePath ?? process.env.ZCODE_BIN ?? process.env.ZCODE_PATH ?? "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs";
768
774
  const zcode = await commandOutput(zcodePath, ["--version"]);
769
775
  const native = await readFile(zcodePath);
770
776
  const sha256 = createHash("sha256").update(native).digest("hex");
771
777
  const qualification = zcodeLifecycleQualification(sha256, zcodeAcpCommit, ddHarness);
772
- return { compatible: qualification.status === "qualified", lifecycle_qualification: qualification, observed_runtime: { zcode, zcode_acp: zcodeAcp, zcode_acp_commit: zcodeAcpCommit, dd_harness_contract: ddHarness } };
778
+ return { compatible: qualification.status === "qualified", contract_supported: supportedZcodeHarnessContract(ddHarness), lifecycle_qualification: qualification, observed_runtime: { zcode, zcode_acp: zcodeAcp, zcode_acp_commit: zcodeAcpCommit, dd_harness_contract: ddHarness } };
773
779
  }
774
780
 
775
781
  export async function createSession(options) {
@@ -804,7 +810,7 @@ export async function createSessionWithBridge(bridge, options, initialized) {
804
810
  const profile_receipt = await applyProfile(bridge, created.sessionId, requested);
805
811
  forward = flowForwarder(options, providerSessionId, bridge);
806
812
  let usage = null, usage_observation_error = null;
807
- try { usage = await bridge.request("zcode/session/usage", { sessionId: created.sessionId }); }
813
+ try { usage = await readZcodeUsage(bridge, created.sessionId); }
808
814
  catch (error) { usage_observation_error = { code: error.code ?? "usage_observation_failed", message: error.message ?? String(error) }; }
809
815
  const usage_ingest_error = await forwardUsageBestEffort(options, providerSessionId, usage, bridge.toolSummary(created.sessionId));
810
816
  await bridge.flush();
@@ -832,7 +838,7 @@ export async function promptSessionWithBridge(bridge, options) {
832
838
  const profile_receipt = await applyProfile(bridge, identity.adapterSessionId, requested, options.assertDispatch);
833
839
  options.assertDispatch?.();
834
840
  let initial_usage_error = null;
835
- try { initial_usage_error = await forwardUsageBestEffort(options, identity.providerSessionId, await bridge.request("zcode/session/usage", { sessionId: identity.adapterSessionId }), bridge.toolSummary(identity.adapterSessionId)); }
841
+ try { initial_usage_error = await forwardUsageBestEffort(options, identity.providerSessionId, await readZcodeUsage(bridge, identity.adapterSessionId), bridge.toolSummary(identity.adapterSessionId)); }
836
842
  catch (error) { initial_usage_error = { code: error.code ?? "usage_observation_failed", message: error.message ?? String(error) }; }
837
843
  // This is the request budget. ACP silence is observed separately and must
838
844
  // never turn a long native child request into a fabricated provider failure.
@@ -0,0 +1,54 @@
1
+ /** Accounting policy over native evidence; the bridge only exposes native RPCs. */
2
+ export function normalizeZcodeUsage(usage, read) {
3
+ const result = { ...usage, requestUsageStatus: "unavailable" };
4
+ const totals = { requestTotalTokens: 0, requestInputTokens: 0, requestOutputTokens: 0,
5
+ requestReasoningTokens: 0, requestCacheCreationTokens: 0, requestCacheReadTokens: 0, requestCount: 0 };
6
+ const seen = new Map();
7
+ let incomplete = !Array.isArray(read?.messages);
8
+ for (const message of Array.isArray(read?.messages) ? read.messages : []) {
9
+ const info = message?.info;
10
+ if (info?.role !== "assistant") continue;
11
+ // Native history includes system timeline markers with role=assistant and
12
+ // zero token placeholders, but no model request (and no total counter).
13
+ if (info.semantics?.origin === "system" && info.semantics?.kind === "timeline_event") continue;
14
+ if (!info.tokens) { incomplete = true; continue; }
15
+ const tokens = info.tokens;
16
+ const id = info.messageId ?? info.id ?? message.id;
17
+ if (id) {
18
+ const signature = JSON.stringify(tokens);
19
+ if (seen.has(id)) { if (seen.get(id) !== signature) incomplete = true; continue; }
20
+ seen.set(id, signature);
21
+ }
22
+ const values = [tokens.total, tokens.input, tokens.output];
23
+ if (!values.every(valid)) { incomplete = true; continue; }
24
+ totals.requestTotalTokens += tokens.total;
25
+ totals.requestInputTokens += tokens.input;
26
+ totals.requestOutputTokens += tokens.output;
27
+ for (const [key, value] of [["requestReasoningTokens", tokens.reasoning],
28
+ ["requestCacheCreationTokens", tokens.cache?.write], ["requestCacheReadTokens", tokens.cache?.read]]) {
29
+ if (value !== undefined && !valid(value)) incomplete = true;
30
+ else totals[key] += value ?? 0;
31
+ }
32
+ totals.requestCount++;
33
+ }
34
+ if (totals.requestCount && !incomplete) Object.assign(result, totals, { requestUsageStatus: "measured" });
35
+ if (incomplete) result.usageIsIncomplete = true;
36
+ return result;
37
+ }
38
+
39
+ function valid(value) { return typeof value === "number" && Number.isFinite(value) && value >= 0; }
40
+
41
+ export async function readZcodeUsage(bridge, sessionId) {
42
+ const usage = await bridge.request("zcode/session/usage", { sessionId });
43
+ // Compatibility with immutable @1 bridges: they already expose this projection.
44
+ if (usage?.requestUsageStatus === "measured") return usage;
45
+ try {
46
+ // Unbounded native read is intentional: accounting needs the full history,
47
+ // unlike turn liveness reads which may use messageLimit: 1.
48
+ const read = await bridge.request("zcode/session/read", { sessionId });
49
+ return normalizeZcodeUsage(usage, read);
50
+ } catch (error) {
51
+ return { ...usage, requestUsageStatus: "unavailable", usageIsIncomplete: true,
52
+ requestUsageError: { code: error.code ?? "usage_evidence_failed", message: error.message, ...(error.details ? { details: error.details } : {}) } };
53
+ }
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.91",
3
+ "version": "0.9.0-beta.93",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {