@deksden-com/dd-flow-cli 0.9.0-beta.92 → 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,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 0.9.0-beta.92
|
|
4
10
|
|
|
5
11
|
### 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");
|
|
@@ -768,12 +770,12 @@ export async function doctor(options = {}) {
|
|
|
768
770
|
const zcodeAcp = await commandOutput(options.bin, ["--version"]);
|
|
769
771
|
const zcodeAcpCommit = await commandOutput(options.bin, ["--dd-harness-commit"]);
|
|
770
772
|
const ddHarness = await commandOutput(options.bin, ["--dd-harness-version"]);
|
|
771
|
-
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";
|
|
772
774
|
const zcode = await commandOutput(zcodePath, ["--version"]);
|
|
773
775
|
const native = await readFile(zcodePath);
|
|
774
776
|
const sha256 = createHash("sha256").update(native).digest("hex");
|
|
775
777
|
const qualification = zcodeLifecycleQualification(sha256, zcodeAcpCommit, ddHarness);
|
|
776
|
-
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 } };
|
|
777
779
|
}
|
|
778
780
|
|
|
779
781
|
export async function createSession(options) {
|
|
@@ -808,7 +810,7 @@ export async function createSessionWithBridge(bridge, options, initialized) {
|
|
|
808
810
|
const profile_receipt = await applyProfile(bridge, created.sessionId, requested);
|
|
809
811
|
forward = flowForwarder(options, providerSessionId, bridge);
|
|
810
812
|
let usage = null, usage_observation_error = null;
|
|
811
|
-
try { usage = await bridge
|
|
813
|
+
try { usage = await readZcodeUsage(bridge, created.sessionId); }
|
|
812
814
|
catch (error) { usage_observation_error = { code: error.code ?? "usage_observation_failed", message: error.message ?? String(error) }; }
|
|
813
815
|
const usage_ingest_error = await forwardUsageBestEffort(options, providerSessionId, usage, bridge.toolSummary(created.sessionId));
|
|
814
816
|
await bridge.flush();
|
|
@@ -836,7 +838,7 @@ export async function promptSessionWithBridge(bridge, options) {
|
|
|
836
838
|
const profile_receipt = await applyProfile(bridge, identity.adapterSessionId, requested, options.assertDispatch);
|
|
837
839
|
options.assertDispatch?.();
|
|
838
840
|
let initial_usage_error = null;
|
|
839
|
-
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)); }
|
|
840
842
|
catch (error) { initial_usage_error = { code: error.code ?? "usage_observation_failed", message: error.message ?? String(error) }; }
|
|
841
843
|
// This is the request budget. ACP silence is observed separately and must
|
|
842
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
|
+
}
|