@pruddiman/hem 0.0.1-beta-697e946 → 0.0.1-beta-1aff12a
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/dist/agents/arbiter-agent.d.ts +10 -39
- package/dist/agents/arbiter-agent.js +10 -78
- package/dist/agents/architecture-agent.d.ts +1 -1
- package/dist/agents/architecture-agent.js +14 -3
- package/dist/agents/base-arbiter.d.ts +82 -0
- package/dist/agents/base-arbiter.js +101 -0
- package/dist/agents/crossref-agent.d.ts +0 -5
- package/dist/agents/crossref-agent.js +19 -48
- package/dist/agents/crossref-arbiter-agent.d.ts +10 -39
- package/dist/agents/crossref-arbiter-agent.js +10 -78
- package/dist/agents/documentation-agent.d.ts +1 -9
- package/dist/agents/documentation-agent.js +8 -17
- package/dist/agents/index-agent.d.ts +1 -1
- package/dist/agents/index-agent.js +20 -4
- package/dist/agents/organization-agent.d.ts +0 -5
- package/dist/agents/organization-agent.js +20 -49
- package/dist/agents/parallel-coordinator.d.ts +47 -0
- package/dist/agents/parallel-coordinator.js +77 -0
- package/dist/auth.d.ts +13 -4
- package/dist/auth.js +59 -17
- package/dist/grouping.d.ts +54 -1
- package/dist/grouping.js +109 -3
- package/dist/index.d.ts +18 -6
- package/dist/index.js +13 -51
- package/dist/orchestrator.d.ts +40 -41
- package/dist/orchestrator.js +93 -126
- package/dist/output.js +5 -13
- package/dist/providers/copilot.d.ts +7 -2
- package/dist/providers/copilot.js +11 -17
- package/dist/providers/index.d.ts +31 -0
- package/dist/providers/index.js +34 -0
- package/dist/session.js +8 -14
- package/dist/types.d.ts +34 -10
- package/package.json +2 -2
|
@@ -12,53 +12,24 @@
|
|
|
12
12
|
* 2. `wrapUp()` — called after all workers complete. Sends a final
|
|
13
13
|
* prompt so the arbiter can issue any remaining
|
|
14
14
|
* DECISION messages. Non-fatal on failure.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle methods are inherited from {@link BaseArbiter}; this class
|
|
17
|
+
* supplies only the configuration values and the prompt body.
|
|
15
18
|
*/
|
|
16
|
-
import type
|
|
17
|
-
import { BaseAgent } from "./base-agent.js";
|
|
18
|
-
import type { WorkerAssignment } from "./organization-agent.js";
|
|
19
|
+
import { BaseArbiter, type BaseArbiterPromptParams } from "./base-arbiter.js";
|
|
19
20
|
/** Parameters for the arbiter prompt. */
|
|
20
|
-
export
|
|
21
|
-
/** Project name. */
|
|
22
|
-
projectName: string;
|
|
23
|
-
/** Absolute path to the destination directory. */
|
|
24
|
-
destinationPath: string;
|
|
25
|
-
/** ALL documentation files (for full awareness). */
|
|
26
|
-
allDocFiles: string[];
|
|
27
|
-
/** Worker assignments mapping each worker label to its owned files. */
|
|
28
|
-
workerAssignments: WorkerAssignment[];
|
|
29
|
-
}
|
|
21
|
+
export type ArbiterPromptParams = BaseArbiterPromptParams;
|
|
30
22
|
/**
|
|
31
23
|
* A coordinator agent that monitors worker broadcasts and issues
|
|
32
24
|
* DECISION directives to resolve cross-subset issues.
|
|
33
25
|
*
|
|
34
26
|
* Does NOT edit files — directs workers to make all changes.
|
|
35
27
|
*/
|
|
36
|
-
export declare class ArbiterAgent extends
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* The initial prompt is sent via `promptAsync` (fire-and-forget) because
|
|
42
|
-
* the arbiter sits idle until it receives broadcasts from workers.
|
|
43
|
-
*
|
|
44
|
-
* @returns The session ID so the caller can register it in the SSE
|
|
45
|
-
* relay map and later call `wrapUp()`.
|
|
46
|
-
* @throws {AuthExpiredError} If session creation or the initial prompt
|
|
47
|
-
* fails due to authentication expiry.
|
|
48
|
-
*/
|
|
49
|
-
run(params: ArbiterPromptParams, verbose?: (msg: string) => void): Promise<{
|
|
50
|
-
sessionId: string;
|
|
51
|
-
}>;
|
|
52
|
-
/**
|
|
53
|
-
* Send the wrap-up prompt after all workers have completed.
|
|
54
|
-
*
|
|
55
|
-
* Asks the arbiter to issue any remaining DECISION messages and then
|
|
56
|
-
* output its final `{ "status": "complete" }` response.
|
|
57
|
-
*
|
|
58
|
-
* This is intentionally **non-fatal** — if the arbiter fails, workers
|
|
59
|
-
* have already produced their manifests and the pipeline can continue.
|
|
60
|
-
*/
|
|
61
|
-
wrapUp(sessionId: string, verbose?: (msg: string) => void): Promise<void>;
|
|
28
|
+
export declare class ArbiterAgent extends BaseArbiter<ArbiterPromptParams> {
|
|
29
|
+
protected readonly tag = "arbiter";
|
|
30
|
+
protected readonly sessionTitle = "Hem: arbiter";
|
|
31
|
+
protected readonly agentField = "hem-org";
|
|
32
|
+
protected makePrompt(params: ArbiterPromptParams): string;
|
|
62
33
|
/**
|
|
63
34
|
* Builds the arbiter prompt.
|
|
64
35
|
*
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
* 2. `wrapUp()` — called after all workers complete. Sends a final
|
|
13
13
|
* prompt so the arbiter can issue any remaining
|
|
14
14
|
* DECISION messages. Non-fatal on failure.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle methods are inherited from {@link BaseArbiter}; this class
|
|
17
|
+
* supplies only the configuration values and the prompt body.
|
|
15
18
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import { BaseAgent } from "./base-agent.js";
|
|
19
|
+
import { BaseArbiter, } from "./base-arbiter.js";
|
|
18
20
|
// ── Agent ───────────────────────────────────────────────────────────────
|
|
19
21
|
/**
|
|
20
22
|
* A coordinator agent that monitors worker broadcasts and issues
|
|
@@ -22,82 +24,12 @@ import { BaseAgent } from "./base-agent.js";
|
|
|
22
24
|
*
|
|
23
25
|
* Does NOT edit files — directs workers to make all changes.
|
|
24
26
|
*/
|
|
25
|
-
export class ArbiterAgent extends
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
* The initial prompt is sent via `promptAsync` (fire-and-forget) because
|
|
33
|
-
* the arbiter sits idle until it receives broadcasts from workers.
|
|
34
|
-
*
|
|
35
|
-
* @returns The session ID so the caller can register it in the SSE
|
|
36
|
-
* relay map and later call `wrapUp()`.
|
|
37
|
-
* @throws {AuthExpiredError} If session creation or the initial prompt
|
|
38
|
-
* fails due to authentication expiry.
|
|
39
|
-
*/
|
|
40
|
-
async run(params, verbose) {
|
|
41
|
-
const tag = "arbiter";
|
|
42
|
-
// 1. Build prompt
|
|
43
|
-
const prompt = ArbiterAgent.buildPrompt(params);
|
|
44
|
-
if (verbose) {
|
|
45
|
-
verbose(`[${tag}] prompt ${prompt.length.toLocaleString()} chars`);
|
|
46
|
-
}
|
|
47
|
-
// 2. Create session
|
|
48
|
-
const sessionId = await this.createSession("Hem: arbiter");
|
|
49
|
-
if (verbose) {
|
|
50
|
-
verbose(`[${tag}] session ${sessionId}`);
|
|
51
|
-
}
|
|
52
|
-
// 3. Fire the initial prompt (fire-and-forget — arbiter waits for broadcasts)
|
|
53
|
-
try {
|
|
54
|
-
await this.provider.session.promptAsync({
|
|
55
|
-
path: { id: sessionId },
|
|
56
|
-
body: {
|
|
57
|
-
parts: [{ type: "text", text: prompt }],
|
|
58
|
-
agent: "hem-org",
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
if (isAuthExpired(err)) {
|
|
64
|
-
throw new AuthExpiredError("the configured provider", err);
|
|
65
|
-
}
|
|
66
|
-
throw err;
|
|
67
|
-
}
|
|
68
|
-
if (verbose) {
|
|
69
|
-
verbose(`[${tag}] initial prompt sent (listening for broadcasts)`);
|
|
70
|
-
}
|
|
71
|
-
return { sessionId };
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Send the wrap-up prompt after all workers have completed.
|
|
75
|
-
*
|
|
76
|
-
* Asks the arbiter to issue any remaining DECISION messages and then
|
|
77
|
-
* output its final `{ "status": "complete" }` response.
|
|
78
|
-
*
|
|
79
|
-
* This is intentionally **non-fatal** — if the arbiter fails, workers
|
|
80
|
-
* have already produced their manifests and the pipeline can continue.
|
|
81
|
-
*/
|
|
82
|
-
async wrapUp(sessionId, verbose) {
|
|
83
|
-
const tag = "arbiter";
|
|
84
|
-
if (verbose) {
|
|
85
|
-
verbose(`[${tag}] all workers done, sending wrap-up prompt`);
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
const wrapUpResponse = await this.provider.prompt(sessionId, "All workers have completed their tasks. If you have any remaining " +
|
|
89
|
-
"DECISION messages to issue, broadcast them now. Otherwise, output your " +
|
|
90
|
-
'final `{ "status": "complete" }` response.', { agent: "hem-org" });
|
|
91
|
-
if (verbose) {
|
|
92
|
-
verbose(`[${tag}] wrap-up response (${(wrapUpResponse ?? "").length} chars)`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
// Arbiter failure is not fatal — workers already produced manifests
|
|
97
|
-
if (verbose) {
|
|
98
|
-
verbose(`[${tag}] ⚠ wrap-up failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
27
|
+
export class ArbiterAgent extends BaseArbiter {
|
|
28
|
+
tag = "arbiter";
|
|
29
|
+
sessionTitle = "Hem: arbiter";
|
|
30
|
+
agentField = "hem-org";
|
|
31
|
+
makePrompt(params) {
|
|
32
|
+
return ArbiterAgent.buildPrompt(params);
|
|
101
33
|
}
|
|
102
34
|
/**
|
|
103
35
|
* Builds the arbiter prompt.
|
|
@@ -144,5 +144,5 @@ export declare class ArchitectureAgent extends BaseAgent {
|
|
|
144
144
|
* normal-mode `buildPrompt()`, but substitutes chunk summaries for
|
|
145
145
|
* the raw findings.
|
|
146
146
|
*/
|
|
147
|
-
static buildSynthesisPrompt(params: ArchPromptParams, chunkSummaries: ArchChunkSummary[]): string;
|
|
147
|
+
static buildSynthesisPrompt(params: ArchPromptParams, chunkSummaries: ArchChunkSummary[], failedChunks?: number): string;
|
|
148
148
|
}
|
|
@@ -120,6 +120,7 @@ export class ArchitectureAgent extends BaseAgent {
|
|
|
120
120
|
// Collect results; use empty fallback for failed chunks so synthesis still runs.
|
|
121
121
|
// Re-throw AuthExpiredError immediately.
|
|
122
122
|
const chunkSummaries = [];
|
|
123
|
+
let failedChunks = 0;
|
|
123
124
|
for (let i = 0; i < settled.length; i++) {
|
|
124
125
|
const result = settled[i];
|
|
125
126
|
if (result.status === "fulfilled") {
|
|
@@ -129,19 +130,26 @@ export class ArchitectureAgent extends BaseAgent {
|
|
|
129
130
|
if (result.reason instanceof AuthExpiredError) {
|
|
130
131
|
throw result.reason;
|
|
131
132
|
}
|
|
133
|
+
failedChunks++;
|
|
134
|
+
const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
132
135
|
if (verbose) {
|
|
133
|
-
const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
134
136
|
verbose(`[${tag}] chunk-${i + 1}/${chunks.length} failed: ${msg}`);
|
|
135
137
|
}
|
|
136
138
|
// Push an empty fallback so the synthesis index is not skewed
|
|
137
139
|
chunkSummaries.push(ArchitectureAgent.parseChunkSummary("", i));
|
|
138
140
|
}
|
|
139
141
|
}
|
|
142
|
+
// Always surface chunk failures to stderr so users running without --verbose
|
|
143
|
+
// know the architecture overview was synthesized from incomplete data.
|
|
144
|
+
if (failedChunks > 0) {
|
|
145
|
+
console.warn(`[${tag}] WARNING: ${failedChunks}/${chunks.length} chunk summarisation(s) failed; ` +
|
|
146
|
+
`architecture overview will be synthesized from incomplete data.`);
|
|
147
|
+
}
|
|
140
148
|
// 3. Final synthesis — combine chunk summaries into architecture.md
|
|
141
149
|
if (verbose) {
|
|
142
150
|
verbose(`[${tag}] Running synthesis from ${chunkSummaries.length} chunk summaries`);
|
|
143
151
|
}
|
|
144
|
-
const synthesisPrompt = ArchitectureAgent.buildSynthesisPrompt(params, chunkSummaries);
|
|
152
|
+
const synthesisPrompt = ArchitectureAgent.buildSynthesisPrompt(params, chunkSummaries, failedChunks);
|
|
145
153
|
if (verbose) {
|
|
146
154
|
verbose(`[${tag}] Synthesis prompt: ${synthesisPrompt.length.toLocaleString()} chars`);
|
|
147
155
|
}
|
|
@@ -378,11 +386,14 @@ export class ArchitectureAgent extends BaseAgent {
|
|
|
378
386
|
* normal-mode `buildPrompt()`, but substitutes chunk summaries for
|
|
379
387
|
* the raw findings.
|
|
380
388
|
*/
|
|
381
|
-
static buildSynthesisPrompt(params, chunkSummaries) {
|
|
389
|
+
static buildSynthesisPrompt(params, chunkSummaries, failedChunks = 0) {
|
|
382
390
|
const { projectName, sourceRoot, destinationPath, allDocFiles } = params;
|
|
383
391
|
const parts = [];
|
|
384
392
|
// 1. System-level instructions
|
|
385
393
|
parts.push(`Generate a comprehensive architecture overview that gives readers a mental model`, `of the entire system before they dive into individual feature or module pages.`, "", `**Write files directly using the edit tool.** Do NOT return Markdown content`, `in the response text. Instead, use the edit tool to create \`architecture.md\``, `in the destination directory. When done, stop.`, "", `**NOTE**: This project is too large for a single-pass analysis. The findings`, `below are pre-summarised from ${chunkSummaries.length} analysis chunks.`, `Synthesize them into a unified architecture overview.`, "");
|
|
394
|
+
if (failedChunks > 0) {
|
|
395
|
+
parts.push(`**DATA QUALITY CAVEAT**: ${failedChunks} of ${chunkSummaries.length} chunk`, `summarisation pass(es) failed and were replaced with empty placeholders.`, `Some areas of the codebase therefore have no findings in this synthesis.`, `Acknowledge this gap explicitly in the architecture overview rather than`, `inventing details about the missing chunks.`, "");
|
|
396
|
+
}
|
|
386
397
|
// 2. Where to write
|
|
387
398
|
parts.push("## Destination", "", `Write the architecture overview to: \`${destinationPath}/architecture.md\``, "");
|
|
388
399
|
// 3. Quality standards (same as buildPrompt)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared base class for Hem's parallel-pass coordinator agents.
|
|
3
|
+
*
|
|
4
|
+
* Both {@link ArbiterAgent} (organization pass) and
|
|
5
|
+
* {@link CrossRefArbiterAgent} (cross-reference pass) follow the same
|
|
6
|
+
* two-phase lifecycle:
|
|
7
|
+
*
|
|
8
|
+
* 1. `run()` — create the session and fire the initial prompt via
|
|
9
|
+
* `promptAsync` (fire-and-forget). Returns `{ sessionId }`
|
|
10
|
+
* so the caller can register it in the SSE relay map.
|
|
11
|
+
* 2. `wrapUp()` — send a final prompt asking the arbiter to issue any
|
|
12
|
+
* remaining DECISION messages. Intentionally non-fatal:
|
|
13
|
+
* workers have already produced their edits.
|
|
14
|
+
*
|
|
15
|
+
* The only differences between the two arbiters are:
|
|
16
|
+
* - log tag (`arbiter` vs `xref-arbiter`)
|
|
17
|
+
* - session title (`Hem: arbiter` vs `Hem: xref-arbiter`)
|
|
18
|
+
* - OpenCode agent field (`hem-org` vs `hem-xref`)
|
|
19
|
+
* - the body of the initial prompt (provided by subclass `makePrompt`)
|
|
20
|
+
*
|
|
21
|
+
* `BaseArbiter` factors out the lifecycle skeleton; subclasses provide
|
|
22
|
+
* the four configuration values above.
|
|
23
|
+
*/
|
|
24
|
+
import type { Provider } from "../providers/types.js";
|
|
25
|
+
import { BaseAgent } from "./base-agent.js";
|
|
26
|
+
import type { WorkerAssignment } from "./organization-agent.js";
|
|
27
|
+
/**
|
|
28
|
+
* Shared shape for arbiter prompt parameters. Both
|
|
29
|
+
* {@link ArbiterPromptParams} and {@link CrossRefArbiterPromptParams}
|
|
30
|
+
* are structural aliases for this type.
|
|
31
|
+
*/
|
|
32
|
+
export interface BaseArbiterPromptParams {
|
|
33
|
+
/** Project name. */
|
|
34
|
+
projectName: string;
|
|
35
|
+
/** Absolute path to the destination directory. */
|
|
36
|
+
destinationPath: string;
|
|
37
|
+
/** ALL documentation files (for full awareness). */
|
|
38
|
+
allDocFiles: string[];
|
|
39
|
+
/** Worker assignments mapping each worker label to its owned files. */
|
|
40
|
+
workerAssignments: WorkerAssignment[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Abstract base for arbiter coordinator agents. Concrete subclasses
|
|
44
|
+
* override the four `protected` configuration fields and `makePrompt`.
|
|
45
|
+
*/
|
|
46
|
+
export declare abstract class BaseArbiter<P extends BaseArbiterPromptParams = BaseArbiterPromptParams> extends BaseAgent {
|
|
47
|
+
/** Short tag used in verbose log lines. */
|
|
48
|
+
protected abstract readonly tag: string;
|
|
49
|
+
/** Human-readable session title shown in the OpenCode session UI. */
|
|
50
|
+
protected abstract readonly sessionTitle: string;
|
|
51
|
+
/** OpenCode agent name routed to in `body.agent`. */
|
|
52
|
+
protected abstract readonly agentField: string;
|
|
53
|
+
constructor(provider: Provider);
|
|
54
|
+
/**
|
|
55
|
+
* Subclass hook: build the initial prompt body. Called once from {@link run}.
|
|
56
|
+
*/
|
|
57
|
+
protected abstract makePrompt(params: P): string;
|
|
58
|
+
/**
|
|
59
|
+
* Create the arbiter session and fire its initial prompt.
|
|
60
|
+
*
|
|
61
|
+
* The initial prompt uses `promptAsync` (fire-and-forget) because the
|
|
62
|
+
* arbiter sits idle until it receives broadcasts from workers.
|
|
63
|
+
*
|
|
64
|
+
* @returns `{ sessionId }` so the caller can register the session for
|
|
65
|
+
* SSE relaying and later call {@link wrapUp}.
|
|
66
|
+
* @throws {AuthExpiredError} If session creation or the initial prompt
|
|
67
|
+
* fails due to authentication expiry.
|
|
68
|
+
*/
|
|
69
|
+
run(params: P, verbose?: (msg: string) => void): Promise<{
|
|
70
|
+
sessionId: string;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Send the wrap-up prompt after all workers have completed.
|
|
74
|
+
*
|
|
75
|
+
* Asks the arbiter to issue any remaining DECISION messages and emit
|
|
76
|
+
* its final `{ "status": "complete" }` response.
|
|
77
|
+
*
|
|
78
|
+
* Intentionally **non-fatal** — workers have already made their edits;
|
|
79
|
+
* a wrap-up failure does not block the pipeline.
|
|
80
|
+
*/
|
|
81
|
+
wrapUp(sessionId: string, verbose?: (msg: string) => void): Promise<void>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared base class for Hem's parallel-pass coordinator agents.
|
|
3
|
+
*
|
|
4
|
+
* Both {@link ArbiterAgent} (organization pass) and
|
|
5
|
+
* {@link CrossRefArbiterAgent} (cross-reference pass) follow the same
|
|
6
|
+
* two-phase lifecycle:
|
|
7
|
+
*
|
|
8
|
+
* 1. `run()` — create the session and fire the initial prompt via
|
|
9
|
+
* `promptAsync` (fire-and-forget). Returns `{ sessionId }`
|
|
10
|
+
* so the caller can register it in the SSE relay map.
|
|
11
|
+
* 2. `wrapUp()` — send a final prompt asking the arbiter to issue any
|
|
12
|
+
* remaining DECISION messages. Intentionally non-fatal:
|
|
13
|
+
* workers have already produced their edits.
|
|
14
|
+
*
|
|
15
|
+
* The only differences between the two arbiters are:
|
|
16
|
+
* - log tag (`arbiter` vs `xref-arbiter`)
|
|
17
|
+
* - session title (`Hem: arbiter` vs `Hem: xref-arbiter`)
|
|
18
|
+
* - OpenCode agent field (`hem-org` vs `hem-xref`)
|
|
19
|
+
* - the body of the initial prompt (provided by subclass `makePrompt`)
|
|
20
|
+
*
|
|
21
|
+
* `BaseArbiter` factors out the lifecycle skeleton; subclasses provide
|
|
22
|
+
* the four configuration values above.
|
|
23
|
+
*/
|
|
24
|
+
import { isAuthExpired, AuthExpiredError } from "../auth.js";
|
|
25
|
+
import { BaseAgent } from "./base-agent.js";
|
|
26
|
+
/**
|
|
27
|
+
* Abstract base for arbiter coordinator agents. Concrete subclasses
|
|
28
|
+
* override the four `protected` configuration fields and `makePrompt`.
|
|
29
|
+
*/
|
|
30
|
+
export class BaseArbiter extends BaseAgent {
|
|
31
|
+
constructor(provider) {
|
|
32
|
+
super(provider);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create the arbiter session and fire its initial prompt.
|
|
36
|
+
*
|
|
37
|
+
* The initial prompt uses `promptAsync` (fire-and-forget) because the
|
|
38
|
+
* arbiter sits idle until it receives broadcasts from workers.
|
|
39
|
+
*
|
|
40
|
+
* @returns `{ sessionId }` so the caller can register the session for
|
|
41
|
+
* SSE relaying and later call {@link wrapUp}.
|
|
42
|
+
* @throws {AuthExpiredError} If session creation or the initial prompt
|
|
43
|
+
* fails due to authentication expiry.
|
|
44
|
+
*/
|
|
45
|
+
async run(params, verbose) {
|
|
46
|
+
const prompt = this.makePrompt(params);
|
|
47
|
+
if (verbose) {
|
|
48
|
+
verbose(`[${this.tag}] prompt ${prompt.length.toLocaleString()} chars`);
|
|
49
|
+
}
|
|
50
|
+
const sessionId = await this.createSession(this.sessionTitle);
|
|
51
|
+
if (verbose) {
|
|
52
|
+
verbose(`[${this.tag}] session ${sessionId}`);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
await this.provider.session.promptAsync({
|
|
56
|
+
path: { id: sessionId },
|
|
57
|
+
body: {
|
|
58
|
+
parts: [{ type: "text", text: prompt }],
|
|
59
|
+
agent: this.agentField,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (isAuthExpired(err)) {
|
|
65
|
+
throw new AuthExpiredError("the configured provider", err);
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
if (verbose) {
|
|
70
|
+
verbose(`[${this.tag}] initial prompt sent (listening for broadcasts)`);
|
|
71
|
+
}
|
|
72
|
+
return { sessionId };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Send the wrap-up prompt after all workers have completed.
|
|
76
|
+
*
|
|
77
|
+
* Asks the arbiter to issue any remaining DECISION messages and emit
|
|
78
|
+
* its final `{ "status": "complete" }` response.
|
|
79
|
+
*
|
|
80
|
+
* Intentionally **non-fatal** — workers have already made their edits;
|
|
81
|
+
* a wrap-up failure does not block the pipeline.
|
|
82
|
+
*/
|
|
83
|
+
async wrapUp(sessionId, verbose) {
|
|
84
|
+
if (verbose) {
|
|
85
|
+
verbose(`[${this.tag}] all workers done, sending wrap-up prompt`);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const wrapUpResponse = await this.provider.prompt(sessionId, "All workers have completed their tasks. If you have any remaining " +
|
|
89
|
+
"DECISION messages to issue, broadcast them now. Otherwise, output your " +
|
|
90
|
+
'final `{ "status": "complete" }` response.', { agent: this.agentField });
|
|
91
|
+
if (verbose) {
|
|
92
|
+
verbose(`[${this.tag}] wrap-up response (${(wrapUpResponse ?? "").length} chars)`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (verbose) {
|
|
97
|
+
verbose(`[${this.tag}] ⚠ wrap-up failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -95,11 +95,6 @@ export declare class CrossRefAgent extends BaseAgent {
|
|
|
95
95
|
* 10. Kills the arbiter session.
|
|
96
96
|
*/
|
|
97
97
|
runParallel(params: CrossRefPromptParams, verbose?: (msg: string) => void): Promise<void>;
|
|
98
|
-
/**
|
|
99
|
-
* Kill a session: abort any running work, then delete the session.
|
|
100
|
-
* Best-effort — failures are logged but not thrown.
|
|
101
|
-
*/
|
|
102
|
-
private killSession;
|
|
103
98
|
/**
|
|
104
99
|
* Spawn a recalled worker session to apply specific fixes.
|
|
105
100
|
*
|
|
@@ -20,6 +20,7 @@ import { BaseAgent } from "./base-agent.js";
|
|
|
20
20
|
import { computeMaxConcurrency } from "../resources.js";
|
|
21
21
|
import { CrossRefArbiterAgent } from "./crossref-arbiter-agent.js";
|
|
22
22
|
import { BROADCAST_TOOL_NAME } from "./organization-agent.js";
|
|
23
|
+
import { buildRecallPrompt, killSession } from "./parallel-coordinator.js";
|
|
23
24
|
// ── Constants ───────────────────────────────────────────────────────────
|
|
24
25
|
/** File count threshold: use parallel workers above this, single agent below. */
|
|
25
26
|
export const XREF_PARALLEL_THRESHOLD = 8;
|
|
@@ -344,7 +345,7 @@ export class CrossRefAgent extends BaseAgent {
|
|
|
344
345
|
// and delete the session.
|
|
345
346
|
completedSessions.add(sessionId);
|
|
346
347
|
allSessions.delete(sessionId);
|
|
347
|
-
await this.
|
|
348
|
+
await killSession(this.provider, sessionId, assignment.label, "xref-parallel", verbose);
|
|
348
349
|
});
|
|
349
350
|
// 6. Wait for all workers to complete
|
|
350
351
|
const results = await Promise.allSettled(workerPromises);
|
|
@@ -367,7 +368,7 @@ export class CrossRefAgent extends BaseAgent {
|
|
|
367
368
|
// 10. Kill the arbiter session
|
|
368
369
|
completedSessions.add(arbiterSessionId);
|
|
369
370
|
allSessions.delete(arbiterSessionId);
|
|
370
|
-
await this.
|
|
371
|
+
await killSession(this.provider, arbiterSessionId, "xref-arbiter", "xref-parallel", verbose);
|
|
371
372
|
// 11. Log any worker failures (the pipeline discovers files via disk scan)
|
|
372
373
|
for (let i = 0; i < results.length; i++) {
|
|
373
374
|
const result = results[i];
|
|
@@ -396,29 +397,6 @@ export class CrossRefAgent extends BaseAgent {
|
|
|
396
397
|
]);
|
|
397
398
|
}
|
|
398
399
|
}
|
|
399
|
-
/**
|
|
400
|
-
* Kill a session: abort any running work, then delete the session.
|
|
401
|
-
* Best-effort — failures are logged but not thrown.
|
|
402
|
-
*/
|
|
403
|
-
async killSession(sessionId, label, verbose) {
|
|
404
|
-
try {
|
|
405
|
-
await this.provider.session.abort({ path: { id: sessionId } });
|
|
406
|
-
}
|
|
407
|
-
catch {
|
|
408
|
-
// Session may already be idle — abort failing is fine
|
|
409
|
-
}
|
|
410
|
-
try {
|
|
411
|
-
await this.provider.session.delete({ path: { id: sessionId } });
|
|
412
|
-
if (verbose) {
|
|
413
|
-
verbose(`[xref-parallel] 🗑 Killed session ${label} (${sessionId.slice(0, 8)}…)`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
catch (err) {
|
|
417
|
-
if (verbose) {
|
|
418
|
-
verbose(`[xref-parallel] ⚠ Failed to delete session ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
400
|
/**
|
|
423
401
|
* Spawn a recalled worker session to apply specific fixes.
|
|
424
402
|
*
|
|
@@ -429,29 +407,22 @@ export class CrossRefAgent extends BaseAgent {
|
|
|
429
407
|
*/
|
|
430
408
|
async runRecalledWorker(projectName, destinationPath, assignment, _allDocFiles, _totalWorkers, instructions, verbose) {
|
|
431
409
|
const tag = `recall-${assignment.label}`;
|
|
432
|
-
const prompt =
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
"## Destination directory",
|
|
437
|
-
"",
|
|
438
|
-
`All documentation files are in: \`${destinationPath}\``,
|
|
439
|
-
"",
|
|
440
|
-
"## Your assigned files",
|
|
441
|
-
"",
|
|
442
|
-
...assignment.files.map((f) => `- \`${destinationPath}/${f}\``),
|
|
443
|
-
"",
|
|
444
|
-
"## Fix instructions from the arbiter",
|
|
445
|
-
"",
|
|
410
|
+
const prompt = buildRecallPrompt({
|
|
411
|
+
destinationPath,
|
|
412
|
+
assignmentLabel: assignment.label,
|
|
413
|
+
assignmentFiles: assignment.files,
|
|
446
414
|
instructions,
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
415
|
+
intro: [
|
|
416
|
+
`Worker **${assignment.label}** (recalled). Previously added cross-reference`,
|
|
417
|
+
`links for **${projectName}** and the arbiter found link consistency issues.`,
|
|
418
|
+
],
|
|
419
|
+
rules: [
|
|
420
|
+
"- Use the edit tool to make the requested fixes directly.",
|
|
421
|
+
"- Only modify files in your assigned list unless the arbiter specifically",
|
|
422
|
+
" instructed otherwise.",
|
|
423
|
+
"- When you have completed all fixes, stop.",
|
|
424
|
+
],
|
|
425
|
+
});
|
|
455
426
|
if (verbose) {
|
|
456
427
|
verbose(`[${tag}] Recall prompt: ${prompt.length.toLocaleString()} chars`);
|
|
457
428
|
}
|
|
@@ -467,7 +438,7 @@ export class CrossRefAgent extends BaseAgent {
|
|
|
467
438
|
}
|
|
468
439
|
finally {
|
|
469
440
|
// Always kill the recall session
|
|
470
|
-
await this.
|
|
441
|
+
await killSession(this.provider, sessionId, tag, "xref-parallel", verbose);
|
|
471
442
|
}
|
|
472
443
|
}
|
|
473
444
|
/**
|
|
@@ -12,53 +12,24 @@
|
|
|
12
12
|
* 2. `wrapUp()` — called after all workers complete. Sends a final
|
|
13
13
|
* prompt so the arbiter can issue any remaining
|
|
14
14
|
* DECISION messages. Non-fatal on failure.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle methods are inherited from {@link BaseArbiter}; this class
|
|
17
|
+
* supplies only the configuration values and the prompt body.
|
|
15
18
|
*/
|
|
16
|
-
import type
|
|
17
|
-
import { BaseAgent } from "./base-agent.js";
|
|
18
|
-
import type { WorkerAssignment } from "./organization-agent.js";
|
|
19
|
+
import { BaseArbiter, type BaseArbiterPromptParams } from "./base-arbiter.js";
|
|
19
20
|
/** Parameters for the cross-ref arbiter prompt. */
|
|
20
|
-
export
|
|
21
|
-
/** Project name. */
|
|
22
|
-
projectName: string;
|
|
23
|
-
/** Absolute path to the destination directory. */
|
|
24
|
-
destinationPath: string;
|
|
25
|
-
/** ALL documentation files (for full awareness). */
|
|
26
|
-
allDocFiles: string[];
|
|
27
|
-
/** Worker assignments mapping each worker label to its owned files. */
|
|
28
|
-
workerAssignments: WorkerAssignment[];
|
|
29
|
-
}
|
|
21
|
+
export type CrossRefArbiterPromptParams = BaseArbiterPromptParams;
|
|
30
22
|
/**
|
|
31
23
|
* A coordinator agent that monitors cross-ref worker broadcasts and issues
|
|
32
24
|
* DECISION directives to resolve cross-worker link consistency issues.
|
|
33
25
|
*
|
|
34
26
|
* Does NOT edit files — directs workers to make all changes.
|
|
35
27
|
*/
|
|
36
|
-
export declare class CrossRefArbiterAgent extends
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* The initial prompt is sent via `promptAsync` (fire-and-forget) because
|
|
42
|
-
* the arbiter sits idle until it receives broadcasts from workers.
|
|
43
|
-
*
|
|
44
|
-
* @returns The session ID so the caller can register it in the SSE
|
|
45
|
-
* relay map and later call `wrapUp()`.
|
|
46
|
-
* @throws {AuthExpiredError} If session creation or the initial prompt
|
|
47
|
-
* fails due to authentication expiry.
|
|
48
|
-
*/
|
|
49
|
-
run(params: CrossRefArbiterPromptParams, verbose?: (msg: string) => void): Promise<{
|
|
50
|
-
sessionId: string;
|
|
51
|
-
}>;
|
|
52
|
-
/**
|
|
53
|
-
* Send the wrap-up prompt after all workers have completed.
|
|
54
|
-
*
|
|
55
|
-
* Asks the arbiter to issue any remaining DECISION messages and then
|
|
56
|
-
* output its final `{ "status": "complete" }` response.
|
|
57
|
-
*
|
|
58
|
-
* This is intentionally **non-fatal** — if the arbiter fails, workers
|
|
59
|
-
* have already made their edits and the pipeline can continue.
|
|
60
|
-
*/
|
|
61
|
-
wrapUp(sessionId: string, verbose?: (msg: string) => void): Promise<void>;
|
|
28
|
+
export declare class CrossRefArbiterAgent extends BaseArbiter<CrossRefArbiterPromptParams> {
|
|
29
|
+
protected readonly tag = "xref-arbiter";
|
|
30
|
+
protected readonly sessionTitle = "Hem: xref-arbiter";
|
|
31
|
+
protected readonly agentField = "hem-xref";
|
|
32
|
+
protected makePrompt(params: CrossRefArbiterPromptParams): string;
|
|
62
33
|
/**
|
|
63
34
|
* Builds the cross-ref arbiter prompt.
|
|
64
35
|
*
|