@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
package/dist/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
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
|
|
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
|
|
757
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|