@agentworkforce/cli 0.10.0 → 0.12.0

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.
@@ -0,0 +1,53 @@
1
+ import type { Harness, PersonaSelection } from '@agentworkforce/workload-router';
2
+ export declare const LAUNCH_METADATA_INTERVAL_MS = 1000;
3
+ export declare const LAUNCH_METADATA_OPT_OUT_ENV = "AGENTWORKFORCE_LAUNCH_METADATA";
4
+ export type LaunchMetadataIngestHarness = 'claude-code' | 'codex' | 'opencode';
5
+ export type LaunchMetadataPendingStampHarness = Harness;
6
+ export interface LaunchMetadataPendingStampOptions {
7
+ harness: LaunchMetadataPendingStampHarness;
8
+ cwd: string;
9
+ enrichment: Record<string, string>;
10
+ sessionDirHint?: string;
11
+ spawnStartTs?: string;
12
+ spawnerPid?: number;
13
+ }
14
+ export interface LaunchMetadataIngestOptions {
15
+ harness: LaunchMetadataIngestHarness;
16
+ }
17
+ export interface LaunchMetadataBackendLike {
18
+ writePendingStamp?: (opts: LaunchMetadataPendingStampOptions) => unknown | Promise<unknown>;
19
+ ingest?: (opts?: LaunchMetadataIngestOptions) => unknown | Promise<unknown>;
20
+ }
21
+ export interface LaunchMetadataStartOptions {
22
+ selection: Pick<PersonaSelection, 'personaId' | 'tier' | 'runtime'>;
23
+ personaSpec: unknown;
24
+ personaSource: string;
25
+ cwd: string;
26
+ noLaunchMetadata?: boolean;
27
+ env?: NodeJS.ProcessEnv;
28
+ sdk?: LaunchMetadataBackendLike | (() => Promise<LaunchMetadataBackendLike>);
29
+ intervalMs?: number;
30
+ now?: () => Date;
31
+ onWarn?: (message: string) => void;
32
+ }
33
+ export interface LaunchMetadataRun {
34
+ readonly enabled: boolean;
35
+ readonly metadata: Record<string, string>;
36
+ stop(): Promise<void>;
37
+ }
38
+ export declare function shouldRecordLaunchMetadata(input: {
39
+ noLaunchMetadata?: boolean;
40
+ env?: NodeJS.ProcessEnv;
41
+ }): boolean;
42
+ export declare function canonicalJson(value: unknown): string;
43
+ export declare function personaVersionHash(personaSpec: unknown): string;
44
+ export declare function personaVersionShort(personaSpec: unknown): string;
45
+ export declare function buildLaunchMetadata(input: {
46
+ selection: Pick<PersonaSelection, 'personaId' | 'tier'>;
47
+ personaSpec: unknown;
48
+ personaSource: string;
49
+ }): Record<string, string>;
50
+ export declare function launchMetadataIngestHarness(harness: Harness): LaunchMetadataIngestHarness;
51
+ export declare function launchMetadataSessionDirHint(harness: Harness): string | undefined;
52
+ export declare function startLaunchMetadataRecording(options: LaunchMetadataStartOptions): Promise<LaunchMetadataRun>;
53
+ //# sourceMappingURL=launch-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-metadata.d.ts","sourceRoot":"","sources":["../src/launch-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import * as launchMetadataBackendSdk from '@relayburn/sdk';
5
+ export const LAUNCH_METADATA_INTERVAL_MS = 1_000;
6
+ export const LAUNCH_METADATA_OPT_OUT_ENV = 'AGENTWORKFORCE_LAUNCH_METADATA';
7
+ const LAUNCH_METADATA_BACKEND_CALL_TIMEOUT_MS = 5_000;
8
+ const NOOP_RUN = Object.freeze({
9
+ enabled: false,
10
+ metadata: Object.freeze({}),
11
+ async stop() {
12
+ /* no-op */
13
+ }
14
+ });
15
+ export function shouldRecordLaunchMetadata(input) {
16
+ if (input.noLaunchMetadata === true)
17
+ return false;
18
+ const env = input.env ?? process.env;
19
+ return env[LAUNCH_METADATA_OPT_OUT_ENV] !== '0';
20
+ }
21
+ export function canonicalJson(value) {
22
+ return JSON.stringify(canonicalize(value));
23
+ }
24
+ export function personaVersionHash(personaSpec) {
25
+ return createHash('sha256').update(canonicalJson(personaSpec)).digest('hex');
26
+ }
27
+ export function personaVersionShort(personaSpec) {
28
+ return personaVersionHash(personaSpec).slice(0, 12);
29
+ }
30
+ export function buildLaunchMetadata(input) {
31
+ return {
32
+ agentworkforce: '1',
33
+ persona: input.selection.personaId,
34
+ personaTier: input.selection.tier,
35
+ personaVersion: personaVersionHash(input.personaSpec),
36
+ personaSource: input.personaSource
37
+ };
38
+ }
39
+ export function launchMetadataIngestHarness(harness) {
40
+ return harness === 'claude' ? 'claude-code' : harness;
41
+ }
42
+ export function launchMetadataSessionDirHint(harness) {
43
+ const home = homedir();
44
+ switch (harness) {
45
+ case 'claude':
46
+ return join(home, '.claude', 'projects');
47
+ case 'codex':
48
+ return join(home, '.codex', 'sessions');
49
+ case 'opencode':
50
+ return join(home, '.local', 'share', 'opencode', 'storage', 'session');
51
+ default: {
52
+ const _exhaustive = harness;
53
+ return _exhaustive;
54
+ }
55
+ }
56
+ }
57
+ export async function startLaunchMetadataRecording(options) {
58
+ if (!shouldRecordLaunchMetadata(options))
59
+ return NOOP_RUN;
60
+ const metadata = buildLaunchMetadata({
61
+ selection: options.selection,
62
+ personaSpec: options.personaSpec,
63
+ personaSource: options.personaSource
64
+ });
65
+ const warn = makeOnceWarn(options.onWarn ?? ((msg) => process.stderr.write(`warning: ${msg}\n`)));
66
+ let sdk;
67
+ try {
68
+ sdk = await resolveLaunchMetadataBackend(options.sdk);
69
+ }
70
+ catch (err) {
71
+ warn(`launch metadata recording unavailable: ${errorMessage(err)}`);
72
+ return disabledRun(metadata);
73
+ }
74
+ if (typeof sdk.writePendingStamp !== 'function') {
75
+ warn('launch metadata recording unavailable: installed metadata backend does not support launcher metadata yet.');
76
+ return disabledRun(metadata);
77
+ }
78
+ if (typeof sdk.ingest !== 'function') {
79
+ warn('launch metadata recording unavailable: installed metadata backend does not support ingest.');
80
+ return disabledRun(metadata);
81
+ }
82
+ const writePendingStamp = sdk.writePendingStamp.bind(sdk);
83
+ const ingest = sdk.ingest.bind(sdk);
84
+ try {
85
+ await withTimeout(writePendingStamp({
86
+ harness: options.selection.runtime.harness,
87
+ cwd: options.cwd,
88
+ enrichment: metadata,
89
+ sessionDirHint: launchMetadataSessionDirHint(options.selection.runtime.harness),
90
+ spawnStartTs: (options.now?.() ?? new Date()).toISOString(),
91
+ spawnerPid: process.pid
92
+ }), LAUNCH_METADATA_BACKEND_CALL_TIMEOUT_MS, 'writePendingStamp');
93
+ }
94
+ catch (err) {
95
+ warn(`launch metadata stamp failed: ${errorMessage(err)}`);
96
+ return disabledRun(metadata);
97
+ }
98
+ let stopped = false;
99
+ let inFlight;
100
+ let ingestWarned = false;
101
+ const runIngest = async () => {
102
+ try {
103
+ await withTimeout(ingest({ harness: launchMetadataIngestHarness(options.selection.runtime.harness) }), LAUNCH_METADATA_BACKEND_CALL_TIMEOUT_MS, 'ingest');
104
+ }
105
+ catch (err) {
106
+ if (!ingestWarned) {
107
+ ingestWarned = true;
108
+ warn(`launch metadata ingest failed: ${errorMessage(err)}`);
109
+ }
110
+ }
111
+ };
112
+ const tick = () => {
113
+ if (stopped || inFlight)
114
+ return;
115
+ inFlight = runIngest().finally(() => {
116
+ inFlight = undefined;
117
+ });
118
+ };
119
+ const interval = setInterval(tick, options.intervalMs ?? LAUNCH_METADATA_INTERVAL_MS);
120
+ interval.unref?.();
121
+ return {
122
+ enabled: true,
123
+ metadata,
124
+ async stop() {
125
+ if (stopped)
126
+ return;
127
+ stopped = true;
128
+ clearInterval(interval);
129
+ if (inFlight)
130
+ await inFlight;
131
+ await runIngest();
132
+ }
133
+ };
134
+ }
135
+ async function resolveLaunchMetadataBackend(sdk) {
136
+ if (typeof sdk === 'function')
137
+ return await sdk();
138
+ if (sdk)
139
+ return sdk;
140
+ return launchMetadataBackendSdk;
141
+ }
142
+ function disabledRun(metadata) {
143
+ return Object.freeze({
144
+ enabled: false,
145
+ metadata,
146
+ async stop() {
147
+ /* no-op */
148
+ }
149
+ });
150
+ }
151
+ function makeOnceWarn(onWarn) {
152
+ let warned = false;
153
+ return (message) => {
154
+ if (warned)
155
+ return;
156
+ warned = true;
157
+ onWarn(message);
158
+ };
159
+ }
160
+ function errorMessage(err) {
161
+ return err instanceof Error ? err.message : String(err);
162
+ }
163
+ async function withTimeout(value, timeoutMs, label) {
164
+ let timeout;
165
+ try {
166
+ return await Promise.race([
167
+ Promise.resolve(value),
168
+ new Promise((_, reject) => {
169
+ timeout = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
170
+ timeout.unref?.();
171
+ })
172
+ ]);
173
+ }
174
+ finally {
175
+ if (timeout)
176
+ clearTimeout(timeout);
177
+ }
178
+ }
179
+ function canonicalize(value) {
180
+ if (value === null || typeof value !== 'object')
181
+ return value;
182
+ if (Array.isArray(value))
183
+ return value.map((item) => canonicalize(item));
184
+ const out = {};
185
+ for (const key of Object.keys(value).sort()) {
186
+ const child = value[key];
187
+ if (child !== undefined)
188
+ out[key] = canonicalize(child);
189
+ }
190
+ return out;
191
+ }
192
+ //# sourceMappingURL=launch-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-metadata.js","sourceRoot":"","sources":["../src/launch-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=launch-metadata.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-metadata.test.d.ts","sourceRoot":"","sources":["../src/launch-metadata.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,152 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { buildLaunchMetadata, canonicalJson, launchMetadataIngestHarness, personaVersionHash, shouldRecordLaunchMetadata, startLaunchMetadataRecording } from './launch-metadata.js';
4
+ function fakeSelection() {
5
+ return {
6
+ personaId: 'code-reviewer',
7
+ tier: 'best',
8
+ runtime: {
9
+ harness: 'codex',
10
+ model: 'openai-codex/gpt-5.3-codex',
11
+ systemPrompt: 'Review the diff.',
12
+ harnessSettings: {
13
+ reasoning: 'high',
14
+ timeoutSeconds: 1200
15
+ }
16
+ }
17
+ };
18
+ }
19
+ function fakeSpec(overrides = {}) {
20
+ return {
21
+ id: 'code-reviewer',
22
+ intent: 'review',
23
+ tags: ['review'],
24
+ description: 'Reviews code.',
25
+ skills: [],
26
+ tiers: {
27
+ best: fakeSelection().runtime,
28
+ 'best-value': {
29
+ harness: 'opencode',
30
+ model: 'opencode/gpt-5-nano',
31
+ systemPrompt: 'Review concisely.',
32
+ harnessSettings: {
33
+ reasoning: 'medium',
34
+ timeoutSeconds: 900
35
+ }
36
+ },
37
+ minimum: {
38
+ harness: 'opencode',
39
+ model: 'opencode/minimax-m2.5-free',
40
+ systemPrompt: 'Review blockers.',
41
+ harnessSettings: {
42
+ reasoning: 'low',
43
+ timeoutSeconds: 600
44
+ }
45
+ }
46
+ },
47
+ ...overrides
48
+ };
49
+ }
50
+ test('personaVersionHash canonicalizes object keys and changes with effective content', () => {
51
+ assert.equal(canonicalJson({ b: 1, a: { d: 4, c: 3 } }), '{"a":{"c":3,"d":4},"b":1}');
52
+ const left = {
53
+ id: 'p',
54
+ tiers: {
55
+ best: { model: 'm', harness: 'codex' }
56
+ },
57
+ tags: ['review']
58
+ };
59
+ const right = {
60
+ tags: ['review'],
61
+ tiers: {
62
+ best: { harness: 'codex', model: 'm' }
63
+ },
64
+ id: 'p'
65
+ };
66
+ assert.equal(personaVersionHash(left), personaVersionHash(right));
67
+ assert.notEqual(personaVersionHash(left), personaVersionHash({ ...right, tags: ['testing'] }));
68
+ });
69
+ test('buildLaunchMetadata emits the required AgentWorkforce metadata', () => {
70
+ const spec = fakeSpec();
71
+ const metadata = buildLaunchMetadata({
72
+ selection: fakeSelection(),
73
+ personaSpec: spec,
74
+ personaSource: 'dir:1'
75
+ });
76
+ assert.deepEqual(metadata, {
77
+ agentworkforce: '1',
78
+ persona: 'code-reviewer',
79
+ personaTier: 'best',
80
+ personaVersion: personaVersionHash(spec),
81
+ personaSource: 'dir:1'
82
+ });
83
+ });
84
+ test('startLaunchMetadataRecording writes a pending stamp and runs periodic plus final ingest', async (t) => {
85
+ t.mock.timers.enable({ apis: ['setInterval'] });
86
+ const stamps = [];
87
+ const ingests = [];
88
+ const run = await startLaunchMetadataRecording({
89
+ selection: fakeSelection(),
90
+ personaSpec: fakeSpec(),
91
+ personaSource: 'cwd',
92
+ cwd: '/tmp/project',
93
+ intervalMs: 5,
94
+ now: () => new Date('2026-05-08T12:00:00.000Z'),
95
+ sdk: {
96
+ writePendingStamp: (opts) => {
97
+ stamps.push(opts);
98
+ },
99
+ ingest: (opts) => {
100
+ if (opts)
101
+ ingests.push(opts);
102
+ }
103
+ }
104
+ });
105
+ assert.equal(run.enabled, true);
106
+ t.mock.timers.tick(5);
107
+ await run.stop();
108
+ assert.equal(stamps.length, 1);
109
+ assert.equal(stamps[0]?.harness, 'codex');
110
+ assert.equal(stamps[0]?.cwd, '/tmp/project');
111
+ assert.equal(stamps[0]?.spawnStartTs, '2026-05-08T12:00:00.000Z');
112
+ assert.equal(stamps[0]?.enrichment.persona, 'code-reviewer');
113
+ assert.ok(ingests.length >= 2, 'expected at least one periodic ingest plus final ingest');
114
+ assert.deepEqual(ingests.at(-1), { harness: 'codex' });
115
+ });
116
+ test('startLaunchMetadataRecording skips SDK loading and ingest when opted out', async () => {
117
+ let loads = 0;
118
+ const run = await startLaunchMetadataRecording({
119
+ selection: fakeSelection(),
120
+ personaSpec: fakeSpec(),
121
+ personaSource: 'library',
122
+ cwd: '/tmp/project',
123
+ noLaunchMetadata: true,
124
+ sdk: async () => {
125
+ loads += 1;
126
+ throw new Error('should not load');
127
+ }
128
+ });
129
+ await run.stop();
130
+ assert.equal(run.enabled, false);
131
+ assert.equal(loads, 0);
132
+ assert.equal(shouldRecordLaunchMetadata({ env: { AGENTWORKFORCE_LAUNCH_METADATA: '0' } }), false);
133
+ const originalEnvValue = process.env.AGENTWORKFORCE_LAUNCH_METADATA;
134
+ try {
135
+ process.env.AGENTWORKFORCE_LAUNCH_METADATA = '0';
136
+ assert.equal(shouldRecordLaunchMetadata({}), false);
137
+ }
138
+ finally {
139
+ if (originalEnvValue === undefined) {
140
+ delete process.env.AGENTWORKFORCE_LAUNCH_METADATA;
141
+ }
142
+ else {
143
+ process.env.AGENTWORKFORCE_LAUNCH_METADATA = originalEnvValue;
144
+ }
145
+ }
146
+ });
147
+ test('launchMetadataIngestHarness maps AgentWorkforce claude to backend claude-code', () => {
148
+ assert.equal(launchMetadataIngestHarness('claude'), 'claude-code');
149
+ assert.equal(launchMetadataIngestHarness('codex'), 'codex');
150
+ assert.equal(launchMetadataIngestHarness('opencode'), 'opencode');
151
+ });
152
+ //# sourceMappingURL=launch-metadata.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-metadata.test.js","sourceRoot":"","sources":["../src/launch-metadata.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Compact projection of a persona used for picker prompts. The model only
3
+ * needs enough to reason about intent fit — full systemPrompts would inflate
4
+ * the prompt without improving quality.
5
+ */
6
+ export interface PickCandidate {
7
+ id: string;
8
+ intent: string;
9
+ tags: readonly string[];
10
+ description: string;
11
+ }
12
+ export type PickConfidence = 'high' | 'medium' | 'low';
13
+ export type PickResult = {
14
+ kind: 'match';
15
+ personaId: string;
16
+ confidence: 'high' | 'medium';
17
+ reason: string;
18
+ } | {
19
+ kind: 'no-match';
20
+ reason: string;
21
+ } | {
22
+ kind: 'picker-unavailable';
23
+ message: string;
24
+ };
25
+ /**
26
+ * Test seam describing one round-trip to the picker subprocess. Returning a
27
+ * structured shape keeps tests free of real `spawnSync` calls while preserving
28
+ * the stdout/stderr/status surface the parser exercises.
29
+ */
30
+ export interface PickerSubprocessRequest {
31
+ bin: string;
32
+ args: readonly string[];
33
+ systemPrompt: string;
34
+ userPrompt: string;
35
+ timeoutMs: number;
36
+ }
37
+ export interface PickerSubprocessResult {
38
+ status: 'ok' | 'enoent' | 'error';
39
+ stdout: string;
40
+ stderr: string;
41
+ errorMessage?: string;
42
+ }
43
+ export type PickerRunner = (req: PickerSubprocessRequest) => PickerSubprocessResult;
44
+ export interface PickPersonaOptions {
45
+ claudeBin?: string;
46
+ model?: string;
47
+ timeoutMs?: number;
48
+ runner?: PickerRunner;
49
+ }
50
+ export declare function buildUserPrompt(task: string, candidates: readonly PickCandidate[]): string;
51
+ interface PickerJsonResponse {
52
+ persona: string | null;
53
+ confidence: PickConfidence;
54
+ reason: string;
55
+ }
56
+ /**
57
+ * Parse the model's text output. Accepts either a bare JSON object or the
58
+ * Claude CLI `--output-format json` envelope `{type, result, ...}` whose
59
+ * `result` field carries the model text. Returns undefined when the output
60
+ * cannot be coerced to the expected shape — callers translate that into a
61
+ * `no-match` result.
62
+ */
63
+ export declare function parsePickerOutput(stdout: string): PickerJsonResponse | undefined;
64
+ export declare function pickPersona(task: string, candidates: readonly PickCandidate[], opts?: PickPersonaOptions): PickResult;
65
+ export {};
66
+ //# sourceMappingURL=persona-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persona-picker.d.ts","sourceRoot":"","sources":["../src/persona-picker.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEvD,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,uBAAuB,KAAK,sBAAsB,CAAC;AAEpF,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAoBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAQ1F;AAED,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAWD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CA0BhF;AA4BD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,IAAI,GAAE,kBAAuB,GAC5B,UAAU,CA+DZ"}
@@ -0,0 +1,152 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ const DEFAULT_BIN = 'claude';
3
+ // Use the dated model id rather than the `claude-haiku-4-5` alias so the
4
+ // subprocess survives any future change in the Claude CLI's alias resolver.
5
+ // The alias works today but the dated id is strictly more robust.
6
+ const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';
7
+ const DEFAULT_TIMEOUT_MS = 30_000;
8
+ const SYSTEM_PROMPT = `You pick the single best persona for a user task from a fixed catalog.
9
+
10
+ Rules:
11
+ - Read the task and the candidate list.
12
+ - Choose the persona whose intent and description most directly match the task.
13
+ - If no persona is a clear fit, set "persona" to null with confidence "low".
14
+ - Output ONE JSON object on a single line. No prose, no code fences, no commentary.
15
+
16
+ Output schema:
17
+ {"persona": "<id from the candidate list, or null>", "confidence": "high" | "medium" | "low", "reason": "<one short sentence>"}`;
18
+ export function buildUserPrompt(task, candidates) {
19
+ const compact = candidates.map((c) => ({
20
+ id: c.id,
21
+ intent: c.intent,
22
+ tags: [...c.tags],
23
+ description: c.description
24
+ }));
25
+ return `Task:\n${task.trim()}\n\nCandidates (JSON):\n${JSON.stringify(compact)}`;
26
+ }
27
+ function isPickerJsonResponse(value) {
28
+ if (typeof value !== 'object' || value === null)
29
+ return false;
30
+ const v = value;
31
+ if (v.persona !== null && typeof v.persona !== 'string')
32
+ return false;
33
+ if (v.confidence !== 'high' && v.confidence !== 'medium' && v.confidence !== 'low')
34
+ return false;
35
+ if (typeof v.reason !== 'string')
36
+ return false;
37
+ return true;
38
+ }
39
+ /**
40
+ * Parse the model's text output. Accepts either a bare JSON object or the
41
+ * Claude CLI `--output-format json` envelope `{type, result, ...}` whose
42
+ * `result` field carries the model text. Returns undefined when the output
43
+ * cannot be coerced to the expected shape — callers translate that into a
44
+ * `no-match` result.
45
+ */
46
+ export function parsePickerOutput(stdout) {
47
+ const trimmed = stdout.trim();
48
+ if (!trimmed)
49
+ return undefined;
50
+ let candidate;
51
+ try {
52
+ candidate = JSON.parse(trimmed);
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ if (typeof candidate === 'object' &&
58
+ candidate !== null &&
59
+ 'result' in candidate &&
60
+ typeof candidate.result === 'string') {
61
+ const inner = candidate.result.trim();
62
+ try {
63
+ candidate = JSON.parse(inner);
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
69
+ return isPickerJsonResponse(candidate) ? candidate : undefined;
70
+ }
71
+ function defaultRunner(req) {
72
+ const opts = {
73
+ encoding: 'utf8',
74
+ timeout: req.timeoutMs
75
+ };
76
+ const child = spawnSync(req.bin, [...req.args], opts);
77
+ if (child.error) {
78
+ const err = child.error;
79
+ if (err.code === 'ENOENT') {
80
+ return { status: 'enoent', stdout: '', stderr: '', errorMessage: err.message };
81
+ }
82
+ return {
83
+ status: 'error',
84
+ stdout: typeof child.stdout === 'string' ? child.stdout : '',
85
+ stderr: typeof child.stderr === 'string' ? child.stderr : '',
86
+ errorMessage: err.message
87
+ };
88
+ }
89
+ const stdout = typeof child.stdout === 'string' ? child.stdout : '';
90
+ const stderr = typeof child.stderr === 'string' ? child.stderr : '';
91
+ if (typeof child.status === 'number' && child.status !== 0) {
92
+ return { status: 'error', stdout, stderr, errorMessage: `exit ${child.status}` };
93
+ }
94
+ return { status: 'ok', stdout, stderr };
95
+ }
96
+ export function pickPersona(task, candidates, opts = {}) {
97
+ if (!task.trim()) {
98
+ return { kind: 'no-match', reason: 'empty task description' };
99
+ }
100
+ if (candidates.length === 0) {
101
+ return { kind: 'no-match', reason: 'no candidates available' };
102
+ }
103
+ const bin = opts.claudeBin ?? DEFAULT_BIN;
104
+ const model = opts.model ?? DEFAULT_MODEL;
105
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
106
+ const runner = opts.runner ?? defaultRunner;
107
+ const userPrompt = buildUserPrompt(task, candidates);
108
+ const args = [
109
+ '-p',
110
+ '--model',
111
+ model,
112
+ '--output-format',
113
+ 'json',
114
+ '--system-prompt',
115
+ SYSTEM_PROMPT,
116
+ userPrompt
117
+ ];
118
+ const result = runner({ bin, args, systemPrompt: SYSTEM_PROMPT, userPrompt, timeoutMs });
119
+ if (result.status === 'enoent') {
120
+ return {
121
+ kind: 'picker-unavailable',
122
+ message: `\`${bin}\` not found on PATH. Install Claude Code (https://docs.claude.com/en/docs/claude-code) or run \`agentworkforce harness check\` to verify.`
123
+ };
124
+ }
125
+ if (result.status === 'error') {
126
+ return {
127
+ kind: 'picker-unavailable',
128
+ message: `picker subprocess failed: ${result.errorMessage ?? 'unknown error'}${result.stderr ? `\n${result.stderr.trim()}` : ''}`
129
+ };
130
+ }
131
+ const parsed = parsePickerOutput(result.stdout);
132
+ if (!parsed) {
133
+ return { kind: 'no-match', reason: 'picker returned no parseable response' };
134
+ }
135
+ if (parsed.persona === null || parsed.confidence === 'low') {
136
+ return { kind: 'no-match', reason: parsed.reason || 'low confidence' };
137
+ }
138
+ const known = new Set(candidates.map((c) => c.id));
139
+ if (!known.has(parsed.persona)) {
140
+ return {
141
+ kind: 'no-match',
142
+ reason: `picker returned unknown persona id "${parsed.persona}"`
143
+ };
144
+ }
145
+ return {
146
+ kind: 'match',
147
+ personaId: parsed.persona,
148
+ confidence: parsed.confidence,
149
+ reason: parsed.reason
150
+ };
151
+ }
152
+ //# sourceMappingURL=persona-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persona-picker.js","sourceRoot":"","sources":["../src/persona-picker.ts"],"names":[],"mappings":"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"}