@hegemonart/get-design-done 1.27.0 → 1.27.1
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/package.json +2 -2
- package/reference/peer-protocols.md +1 -1
- package/scripts/install.cjs +100 -1
- package/scripts/lib/peer-cli/spawn-cmd.cjs +2 -2
- package/scripts/lib/session-runner/index.ts +175 -28
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Get Design Done — 5-stage agent-orchestrated design pipeline with 9 connections, handoff-first workflow, bidirectional Figma write-back, 22+ specialized agents, queryable knowledge layer (intel store, dependency analysis, learnings extraction), and a self-improvement loop (reflector, frontmatter + budget feedback, global-skills layer). v1.20.0 ships the SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream, and resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) for rate-limit + 429 + context-overflow recovery. Full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation (auto-tag + GitHub Release + release-time smoke test).",
|
|
8
|
-
"version": "1.27.
|
|
8
|
+
"version": "1.27.1"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "get-design-done",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), Claude Design handoff, bidirectional Figma write-back, and a queryable intel store (.design/intel/) for dependency and learnings queries. Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation. Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain.",
|
|
15
|
-
"version": "1.27.
|
|
15
|
+
"version": "1.27.1",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "hegemonart"
|
|
18
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get-design-done",
|
|
3
3
|
"short_name": "gdd",
|
|
4
|
-
"version": "1.27.
|
|
4
|
+
"version": "1.27.1",
|
|
5
5
|
"description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), handoff-first workflow via Claude Design bundles, bidirectional Figma write-back (annotations, Code Connect), queryable intel store (`.design/intel/`) for O(1) design surface lookups, and self-improvement loop (reflector agent, frontmatter + budget feedback, global-skills layer at `~/.claude/gdd/global-skills/`). Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings, reflect, apply-reflections. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows, lint + schema + frontmatter + stale-ref + shellcheck + gitleaks + injection-scan + blocking size-budget) and release automation (auto-tag + GitHub Release + release-time smoke test). Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "hegemonart",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to get-design-done are documented here. Versions follow [sem
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [1.27.1] — 2026-04-30
|
|
8
|
+
|
|
9
|
+
Phase 27 wiring patch — closes the production-integration gaps left by v1.27.0's "structural ship". v1.27.0 landed all peer-CLI library code + tests + docs but the helpers were exported without callers, so `delegate_to:` on agent frontmatter was validated and then ignored at runtime. v1.27.1 wires the four integration points so delegation actually fires for users who set `delegate_to:` AND allowlist the peer.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`session-runner.run()` now invokes `tryDelegate` (Plan 27-06 wiring)** — when `opts.delegateTo` is set to a `<peer>-<role>` value AND the registry can route AND the peer is in `.design/config.json#peer_cli.enabled_peers`, the prompt runs on the peer-CLI and `run()` returns the peer result. On peer-absent / peer-error / null result, falls through transparently to the local Anthropic SDK loop (D-07). Previously the `tryDelegate` helper existed in the file but `run()` never called it.
|
|
14
|
+
|
|
15
|
+
- **Real `appendEvent('peer_call_started|complete|failed', ...)` emission (Plan 27-08 wiring)** — replaced the stderr-only placeholder in session-runner with three real event-emission calls. Events flow through Phase 22's `appendEvent()` API using the constants registered in v1.27.0, tagged with `runtime_role: 'peer'` and `peer_id`. Reflector cross-runtime cost-arbitrage (Plan 26-06) now sees peer telemetry. `GDD_PEER_DEBUG=1` continues to mirror the failed events to stderr for live tailing.
|
|
16
|
+
|
|
17
|
+
- **`install.cjs` interactive peer-detection nudge (Plan 27-11 wiring)** — after a successful (non-uninstall, non-dry-run) install in a TTY, scans `peerBinary` paths via `detectInstalledPeers()`. If 1+ peer detected, prompts via `@clack/prompts` with `confirm()` (default: NO). On yes, writes `.design/config.json#peer_cli.enabled_peers`. New `--no-peer-prompt` flag suppresses the prompt entirely (CI-friendly). Silent skip when zero peers detected. Default-NO preserves the opt-in trust contract (D-11).
|
|
18
|
+
|
|
19
|
+
### Out of scope (known, deferred)
|
|
20
|
+
|
|
21
|
+
- **Bandit `pullWithDelegate` caller (Plan 27-07 wiring)** — `pullWithDelegate` and `updateWithDelegate` ship in the bandit module surface (v1.27.0) but no production caller invokes them yet. Wiring requires `gdd-router` SKILL.md change (procedural, not code) which is out of scope for a wiring patch. Phase 28+ territory once the integration shape is decided. The `delegate?` dimension stays exported as a library extension for ad-hoc use.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- Existing 23 peer-CLI session-runner / events / end-to-end tests pass after wiring.
|
|
26
|
+
- Existing 33 install.cjs + peer-detect tests pass after the nudge addition.
|
|
27
|
+
- Full Phase 27 surface tests stay green; no new test files (this is a wiring patch, not a new surface).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
7
31
|
## [1.27.0] — 2026-04-30
|
|
8
32
|
|
|
9
33
|
Phase 27 Peer-CLI Delegation Layer milestone — closes the **outbound** half of multi-runtime. Phase 24 made gdd installable on 14 runtimes; Phase 21 made the same pipeline run on each; Phase 26 made tier→model resolve correctly per runtime. v1.27.0 adds the missing piece: gdd agents OPTIONALLY delegate to local peer CLIs (Codex via App Server Protocol; Gemini/Cursor/Copilot/Qwen via Agent Client Protocol) when measurably cheaper or higher-quality for the role. Falls back to in-process Anthropic SDK when peer is unavailable. Honors Phase 26 tier maps + Phase 22 event chain + Phase 23.5 bandit posterior — `delegate?` becomes another arm in `(agent_type × tier × delegate)` Thompson sampling, no new ML.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hegemonart/get-design-done",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.1",
|
|
4
4
|
"description": "A design-quality pipeline for AI coding agents: brief, plan, implement, and verify UI work against your design system.",
|
|
5
5
|
"author": "Hegemon",
|
|
6
6
|
"homepage": "https://github.com/hegemonart/get-design-done",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"provenance": true
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"test": "node --test --experimental-strip-types \"tests/**/*.cjs\" \"tests/**/*.ts\"",
|
|
40
|
+
"test": "node --test --experimental-strip-types \"tests/**/*.test.cjs\" \"tests/**/*.test.ts\"",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"codegen:schemas": "node --experimental-strip-types scripts/codegen-schema-types.ts",
|
|
43
43
|
"lint:md": "npx --yes markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#.planning\" \"#.claude\" \"#test-fixture/baselines\"",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
For ops-level guidance (when delegation fires, how to enable/disable, fallback diagnostics), see `docs/PEER-DELEGATION.md`.
|
|
6
6
|
|
|
7
|
-
Protocol shapes are adapted from [`greenpolo/cc-multi-cli`](https://github.com/greenpolo/cc-multi-cli) under Apache 2.0 — see `NOTICE` for full attribution.
|
|
7
|
+
Protocol shapes are adapted from [`greenpolo/cc-multi-cli-plugin`](https://github.com/greenpolo/cc-multi-cli-plugin) under Apache 2.0 — see `NOTICE` for full attribution.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/scripts/install.cjs
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
const path = require('node:path');
|
|
20
20
|
|
|
21
|
-
const { listRuntimes, listRuntimeIds } = require('./lib/install/runtimes.cjs');
|
|
21
|
+
const { listRuntimes, listRuntimeIds, detectInstalledPeers, listPeerCapableRuntimes } = require('./lib/install/runtimes.cjs');
|
|
22
22
|
const { installRuntime, uninstallRuntime } = require('./lib/install/installer.cjs');
|
|
23
|
+
const fs = require('node:fs');
|
|
23
24
|
|
|
24
25
|
function parseArgs(argv) {
|
|
25
26
|
const args = argv.slice(2);
|
|
@@ -60,6 +61,7 @@ function helpText() {
|
|
|
60
61
|
' --uninstall Remove the plugin from selected runtimes',
|
|
61
62
|
' --dry-run Print the diff without writing',
|
|
62
63
|
' --config-dir D Override the config directory',
|
|
64
|
+
' --no-peer-prompt Suppress the post-install peer-CLI detection nudge',
|
|
63
65
|
' --help, -h Show this message',
|
|
64
66
|
'',
|
|
65
67
|
'Environment overrides (per-runtime):',
|
|
@@ -196,6 +198,103 @@ async function main() {
|
|
|
196
198
|
'',
|
|
197
199
|
].join('\n'),
|
|
198
200
|
);
|
|
201
|
+
|
|
202
|
+
// v1.27.1 — Plan 27-11 wiring: post-install peer-CLI detection nudge.
|
|
203
|
+
// Fires only on real install (not uninstall, not dry-run) when not
|
|
204
|
+
// suppressed by --no-peer-prompt. Silently skips when no peers detected.
|
|
205
|
+
// Always opt-in: writes .design/config.json#peer_cli.enabled_peers
|
|
206
|
+
// ONLY on explicit y/Y; default is no.
|
|
207
|
+
if (!uninstall && !dryRun && !flags.has('--no-peer-prompt')) {
|
|
208
|
+
try {
|
|
209
|
+
await maybeNudgePeerCli({ flags });
|
|
210
|
+
} catch (e) {
|
|
211
|
+
// Nudge is non-critical. Surface a one-line warning but don't fail
|
|
212
|
+
// the install — the plugin is fully functional without peer-CLI.
|
|
213
|
+
process.stderr.write(
|
|
214
|
+
`\n[peer-cli] post-install nudge skipped: ${e && e.message ? e.message : e}\n`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// v1.27.1 — Plan 27-11: post-install nudge. Detects installed peer CLIs,
|
|
221
|
+
// asks the user (interactive y/N) whether to wire them as peers, writes
|
|
222
|
+
// .design/config.json#peer_cli.enabled_peers on yes. Default = NO (opt-in).
|
|
223
|
+
async function maybeNudgePeerCli({ flags }) {
|
|
224
|
+
const detected = detectInstalledPeers();
|
|
225
|
+
if (!detected || detected.length === 0) {
|
|
226
|
+
// Nothing detected — silent skip. (No bad UX of "we found 0 peers".)
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Build the human-readable peer line for the prompt.
|
|
231
|
+
const allPeerCapable = listPeerCapableRuntimes();
|
|
232
|
+
const detectedDisplay = detected
|
|
233
|
+
.map((id) => {
|
|
234
|
+
const r = allPeerCapable.find((x) => x.id === id);
|
|
235
|
+
return r && r.displayName ? r.displayName : id;
|
|
236
|
+
})
|
|
237
|
+
.join(', ');
|
|
238
|
+
|
|
239
|
+
process.stdout.write(
|
|
240
|
+
[
|
|
241
|
+
'',
|
|
242
|
+
'✓ Detected peer CLIs: ' + detectedDisplay,
|
|
243
|
+
'',
|
|
244
|
+
'gdd v1.27.0 introduced optional peer-CLI delegation. With your',
|
|
245
|
+
'agents\\u2019 frontmatter `delegate_to:` set, gdd can route specific',
|
|
246
|
+
'roles through these peer CLIs (cost or quality wins per Phase 23.5',
|
|
247
|
+
'bandit). You can change this anytime via .design/config.json.',
|
|
248
|
+
'',
|
|
249
|
+
].join('\n'),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Decide interactive vs scripted. shouldUseInteractive lives in this
|
|
253
|
+
// file; reuse it. If non-TTY, default to no (silent opt-out) so CI
|
|
254
|
+
// installers don't hang waiting for input.
|
|
255
|
+
let confirmed = false;
|
|
256
|
+
if (shouldUseInteractive(flags)) {
|
|
257
|
+
try {
|
|
258
|
+
const clack = require('@clack/prompts');
|
|
259
|
+
const ans = await clack.confirm({
|
|
260
|
+
message: 'Enable peer-CLI delegation for these peers?',
|
|
261
|
+
initialValue: false,
|
|
262
|
+
});
|
|
263
|
+
confirmed = (ans === true);
|
|
264
|
+
} catch {
|
|
265
|
+
// @clack/prompts unavailable — silently default to no.
|
|
266
|
+
confirmed = false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!confirmed) {
|
|
271
|
+
process.stdout.write(
|
|
272
|
+
'Skipped — peer-CLI delegation remains disabled.\n' +
|
|
273
|
+
'Enable later by adding to .design/config.json:\n' +
|
|
274
|
+
' { "peer_cli": { "enabled_peers": ' + JSON.stringify(detected) + ' } }\n\n',
|
|
275
|
+
);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Write the allowlist. Merge with any existing .design/config.json.
|
|
280
|
+
const cfgPath = path.join(process.cwd(), '.design', 'config.json');
|
|
281
|
+
let cfg = {};
|
|
282
|
+
try {
|
|
283
|
+
if (fs.existsSync(cfgPath)) {
|
|
284
|
+
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
cfg = {};
|
|
288
|
+
}
|
|
289
|
+
if (!cfg.peer_cli || typeof cfg.peer_cli !== 'object') cfg.peer_cli = {};
|
|
290
|
+
cfg.peer_cli.enabled_peers = detected;
|
|
291
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
292
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
293
|
+
process.stdout.write(
|
|
294
|
+
`✓ Wrote .design/config.json — peer-CLI enabled for: ${detected.join(', ')}\n` +
|
|
295
|
+
' Set delegate_to: <peer>-<role> on agent frontmatter to opt agents in.\n' +
|
|
296
|
+
' See docs/PEER-DELEGATION.md for the full ops guide.\n\n',
|
|
297
|
+
);
|
|
199
298
|
}
|
|
200
299
|
|
|
201
300
|
main().catch((err) => {
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
// string with `shell: true`. We forward-slash the path so Windows shell
|
|
22
22
|
// resolves it correctly even when the path contains backslashes:
|
|
23
23
|
//
|
|
24
|
-
// // BROKEN on Windows for .cmd shims:
|
|
25
|
-
// spawn(
|
|
24
|
+
// // BROKEN on Windows for .cmd shims (absPath ends in .cmd):
|
|
25
|
+
// spawn(absPath, ['app-server'])
|
|
26
26
|
//
|
|
27
27
|
// // WORKS everywhere (.cmd via cmd.exe; non-.cmd via direct exec):
|
|
28
28
|
// const fwd = absPath.replace(/\\/g, '/');
|
|
@@ -200,15 +200,22 @@ async function tryDelegate(args: {
|
|
|
200
200
|
return reg !== null ? reg.dispatch : null;
|
|
201
201
|
})();
|
|
202
202
|
if (dispatcher === null) {
|
|
203
|
-
// No registry available at all — fall through to local.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
_logPeerCallFailed({ peer: parsed.peer, role, errorClass: 'registry_missing' });
|
|
203
|
+
// No registry available at all — fall through to local.
|
|
204
|
+
_logPeerCallFailed({
|
|
205
|
+
peer: parsed.peer, role, errorClass: 'registry_missing',
|
|
206
|
+
sessionId: args.sessionId, stage: opts.stage,
|
|
207
|
+
});
|
|
209
208
|
return null;
|
|
210
209
|
}
|
|
211
210
|
|
|
211
|
+
// v1.27.1 — emit peer_call_started before dispatcher invocation so the
|
|
212
|
+
// events.jsonl trail captures the attempt even if the dispatcher hangs.
|
|
213
|
+
_logPeerCallStarted({
|
|
214
|
+
peer: parsed.peer, role,
|
|
215
|
+
sessionId: args.sessionId, stage: opts.stage,
|
|
216
|
+
});
|
|
217
|
+
const dispatchStartedAt = Date.now();
|
|
218
|
+
|
|
212
219
|
let dispatchResult: { result: unknown; peer: string; protocol: 'acp' | 'asp' } | null = null;
|
|
213
220
|
try {
|
|
214
221
|
dispatchResult = await dispatcher(role, tier, sanitizedPrompt, { cwd: process.cwd() });
|
|
@@ -218,6 +225,8 @@ async function tryDelegate(args: {
|
|
|
218
225
|
role,
|
|
219
226
|
errorClass: 'dispatch_threw',
|
|
220
227
|
message: err instanceof Error ? err.message : String(err),
|
|
228
|
+
sessionId: args.sessionId,
|
|
229
|
+
stage: opts.stage,
|
|
221
230
|
});
|
|
222
231
|
return null; // transparent fallback
|
|
223
232
|
}
|
|
@@ -225,10 +234,28 @@ async function tryDelegate(args: {
|
|
|
225
234
|
if (dispatchResult === null) {
|
|
226
235
|
// Registry returned null — peer absent, capability mismatch, or
|
|
227
236
|
// adapter-side error. Per D-07 we fall back silently.
|
|
228
|
-
_logPeerCallFailed({
|
|
237
|
+
_logPeerCallFailed({
|
|
238
|
+
peer: parsed.peer, role, errorClass: 'registry_returned_null',
|
|
239
|
+
sessionId: args.sessionId, stage: opts.stage,
|
|
240
|
+
});
|
|
229
241
|
return null;
|
|
230
242
|
}
|
|
231
243
|
|
|
244
|
+
// v1.27.1 — peer round-trip succeeded. Emit peer_call_complete with the
|
|
245
|
+
// measured latency. Token counts + cost are 0 / null because adapters
|
|
246
|
+
// don't surface usage in v1.27 (Plan 27-04 spec deferred it); reflector
|
|
247
|
+
// tolerates null cost (Plan 26-06 cost-arbitrage analysis).
|
|
248
|
+
_logPeerCallComplete({
|
|
249
|
+
peer: dispatchResult.peer,
|
|
250
|
+
role,
|
|
251
|
+
latencyMs: Date.now() - dispatchStartedAt,
|
|
252
|
+
tokensIn: 0,
|
|
253
|
+
tokensOut: 0,
|
|
254
|
+
costUsd: null,
|
|
255
|
+
sessionId: args.sessionId,
|
|
256
|
+
stage: opts.stage,
|
|
257
|
+
});
|
|
258
|
+
|
|
232
259
|
// Peer succeeded. Build a SessionResult that mirrors the local path's
|
|
233
260
|
// shape so downstream consumers (stage-handlers, transcript readers,
|
|
234
261
|
// tests) treat both paths uniformly. We do NOT write a transcript file
|
|
@@ -271,34 +298,122 @@ function _coerceFinalText(result: unknown): string | undefined {
|
|
|
271
298
|
}
|
|
272
299
|
|
|
273
300
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
301
|
+
* v1.27.1 — wires Plan 27-08's `peer_call_failed` event for real.
|
|
302
|
+
* Phase 22 `appendEvent` accepts the new event type (registered in
|
|
303
|
+
* KNOWN_EVENT_TYPES via Plan 27-08), so the reflector and downstream
|
|
304
|
+
* telemetry consumers see delegation drops as a measurement signal.
|
|
305
|
+
*
|
|
306
|
+
* Errors from `appendEvent` (e.g., events.jsonl unwritable) are
|
|
307
|
+
* swallowed — peer-call telemetry is observability, not critical
|
|
308
|
+
* path. STATE.md remains the durable record of session outcomes.
|
|
309
|
+
*
|
|
310
|
+
* Operators can additionally set `GDD_PEER_DEBUG=1` to emit a
|
|
311
|
+
* one-line stderr breadcrumb mirroring the event for live tailing.
|
|
281
312
|
*/
|
|
282
313
|
function _logPeerCallFailed(args: {
|
|
283
314
|
peer: string;
|
|
284
315
|
role: string;
|
|
285
316
|
errorClass: string;
|
|
286
317
|
message?: string;
|
|
318
|
+
sessionId?: string;
|
|
319
|
+
stage?: SessionRunnerOptions['stage'];
|
|
287
320
|
}): void {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
321
|
+
try {
|
|
322
|
+
appendEvent({
|
|
323
|
+
type: 'peer_call_failed',
|
|
324
|
+
timestamp: new Date().toISOString(),
|
|
325
|
+
sessionId: args.sessionId ?? 'unknown',
|
|
326
|
+
...(args.stage !== undefined && args.stage !== 'init' && args.stage !== 'custom' ? { stage: args.stage } : {}),
|
|
327
|
+
payload: {
|
|
328
|
+
runtime_role: 'peer',
|
|
329
|
+
peer_id: args.peer,
|
|
330
|
+
role: args.role,
|
|
331
|
+
error_class: args.errorClass,
|
|
332
|
+
...(args.message !== undefined ? { message: args.message } : {}),
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
} catch {
|
|
336
|
+
// Telemetry is best-effort — never let an event-stream failure
|
|
337
|
+
// break the actual session flow.
|
|
338
|
+
}
|
|
339
|
+
if (process.env['GDD_PEER_DEBUG'] === '1') {
|
|
340
|
+
const payload = JSON.stringify({
|
|
341
|
+
type: 'peer_call_failed',
|
|
342
|
+
peer_id: args.peer,
|
|
343
|
+
role: args.role,
|
|
344
|
+
error_class: args.errorClass,
|
|
345
|
+
...(args.message !== undefined ? { message: args.message } : {}),
|
|
346
|
+
ts: new Date().toISOString(),
|
|
347
|
+
});
|
|
348
|
+
// eslint-disable-next-line no-console
|
|
349
|
+
console.error(`[peer-cli] ${payload}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* v1.27.1 — emit `peer_call_started` event. Fired once at the beginning
|
|
355
|
+
* of a delegation attempt, before the dispatcher is invoked. Pairs with
|
|
356
|
+
* `peer_call_complete` (success path) or `peer_call_failed` (any failure
|
|
357
|
+
* path, transparent to caller per D-07).
|
|
358
|
+
*/
|
|
359
|
+
function _logPeerCallStarted(args: {
|
|
360
|
+
peer: string;
|
|
361
|
+
role: string;
|
|
362
|
+
sessionId?: string;
|
|
363
|
+
stage?: SessionRunnerOptions['stage'];
|
|
364
|
+
}): void {
|
|
365
|
+
try {
|
|
366
|
+
appendEvent({
|
|
367
|
+
type: 'peer_call_started',
|
|
368
|
+
timestamp: new Date().toISOString(),
|
|
369
|
+
sessionId: args.sessionId ?? 'unknown',
|
|
370
|
+
...(args.stage !== undefined && args.stage !== 'init' && args.stage !== 'custom' ? { stage: args.stage } : {}),
|
|
371
|
+
payload: {
|
|
372
|
+
runtime_role: 'peer',
|
|
373
|
+
peer_id: args.peer,
|
|
374
|
+
role: args.role,
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
} catch {
|
|
378
|
+
// best-effort
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* v1.27.1 — emit `peer_call_complete` event. Fired after a successful
|
|
384
|
+
* dispatcher round-trip. Cost is null when the adapter doesn't return
|
|
385
|
+
* usage data (some peers don't surface token counts); the reflector
|
|
386
|
+
* tolerates null cost for arbitrage analysis (Plan 26-06).
|
|
387
|
+
*/
|
|
388
|
+
function _logPeerCallComplete(args: {
|
|
389
|
+
peer: string;
|
|
390
|
+
role: string;
|
|
391
|
+
latencyMs: number;
|
|
392
|
+
tokensIn: number;
|
|
393
|
+
tokensOut: number;
|
|
394
|
+
costUsd: number | null;
|
|
395
|
+
sessionId?: string;
|
|
396
|
+
stage?: SessionRunnerOptions['stage'];
|
|
397
|
+
}): void {
|
|
398
|
+
try {
|
|
399
|
+
appendEvent({
|
|
400
|
+
type: 'peer_call_complete',
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
sessionId: args.sessionId ?? 'unknown',
|
|
403
|
+
...(args.stage !== undefined && args.stage !== 'init' && args.stage !== 'custom' ? { stage: args.stage } : {}),
|
|
404
|
+
payload: {
|
|
405
|
+
runtime_role: 'peer',
|
|
406
|
+
peer_id: args.peer,
|
|
407
|
+
role: args.role,
|
|
408
|
+
latency_ms: args.latencyMs,
|
|
409
|
+
tokens_in: args.tokensIn,
|
|
410
|
+
tokens_out: args.tokensOut,
|
|
411
|
+
cost_usd: args.costUsd,
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
} catch {
|
|
415
|
+
// best-effort
|
|
416
|
+
}
|
|
302
417
|
}
|
|
303
418
|
|
|
304
419
|
/** Baseline retry backoff parameters (matches jittered-backoff defaults for
|
|
@@ -623,6 +738,38 @@ export async function run(opts: SessionRunnerOptions): Promise<SessionResult> {
|
|
|
623
738
|
}
|
|
624
739
|
}
|
|
625
740
|
|
|
741
|
+
// -- 6.5. Peer-CLI delegation try (Plan 27-06 wiring, v1.27.1). ---------
|
|
742
|
+
// If the agent's frontmatter declares `delegate_to: <peer>-<role>` AND the
|
|
743
|
+
// peer is allowlisted AND the registry can route, run the prompt on the
|
|
744
|
+
// peer-CLI and return early. On peer-absent / peer-error / null result,
|
|
745
|
+
// fall through transparently to the local SDK loop (D-07).
|
|
746
|
+
//
|
|
747
|
+
// tryDelegate is a no-op when opts.delegateTo is undefined / 'none', when
|
|
748
|
+
// the registry can't load, when the peer isn't allowlisted, when the
|
|
749
|
+
// dispatcher returns null, or when the dispatcher throws. In all those
|
|
750
|
+
// cases tryDelegate returns null and we proceed to the local SDK path.
|
|
751
|
+
const peerResult = await tryDelegate({
|
|
752
|
+
opts,
|
|
753
|
+
sanitizedPrompt,
|
|
754
|
+
transcriptPath,
|
|
755
|
+
sessionId,
|
|
756
|
+
sanitizer: sanResult,
|
|
757
|
+
});
|
|
758
|
+
if (peerResult !== null) {
|
|
759
|
+
emit('session.completed', opts.stage, sessionId, {
|
|
760
|
+
stage: opts.stage,
|
|
761
|
+
sessionId,
|
|
762
|
+
status: peerResult.status,
|
|
763
|
+
turns: peerResult.turns,
|
|
764
|
+
usage: peerResult.usage,
|
|
765
|
+
transcript_path: transcriptPath,
|
|
766
|
+
sanitizer: { applied: [...peerResult.sanitizer.applied], removedSections: [...peerResult.sanitizer.removedSections] },
|
|
767
|
+
});
|
|
768
|
+
transcript.close();
|
|
769
|
+
if (opts.signal !== undefined) opts.signal.removeEventListener('abort', onExternalAbort);
|
|
770
|
+
return peerResult;
|
|
771
|
+
}
|
|
772
|
+
|
|
626
773
|
// -- 7. Retry-once loop. ------------------------------------------------
|
|
627
774
|
const maxAttempts = opts.maxRetries !== undefined && opts.maxRetries > 0
|
|
628
775
|
? opts.maxRetries
|