@rallycry/conveyor-agent 7.0.1 → 7.0.3

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,26 @@
1
+ // src/utils/logger.ts
2
+ function createServiceLogger(service) {
3
+ const prefix = `[conveyor-agent:${service}]`;
4
+ return {
5
+ info(message, data) {
6
+ const extra = data ? ` ${JSON.stringify(data)}` : "";
7
+ process.stderr.write(`${prefix} ${message}${extra}
8
+ `);
9
+ },
10
+ warn(message, data) {
11
+ const extra = data ? ` ${JSON.stringify(data)}` : "";
12
+ process.stderr.write(`${prefix} WARN ${message}${extra}
13
+ `);
14
+ },
15
+ error(message, data) {
16
+ const extra = data ? ` ${JSON.stringify(data)}` : "";
17
+ process.stderr.write(`${prefix} ERROR ${message}${extra}
18
+ `);
19
+ }
20
+ };
21
+ }
22
+
23
+ export {
24
+ createServiceLogger
25
+ };
26
+ //# sourceMappingURL=chunk-CYZPFJGN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/logger.ts"],"sourcesContent":["/** Minimal structured logger for conveyor-agent (writes to stderr). */\nexport function createServiceLogger(service: string) {\n const prefix = `[conveyor-agent:${service}]`;\n return {\n info(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} ${message}${extra}\\n`);\n },\n warn(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} WARN ${message}${extra}\\n`);\n },\n error(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} ERROR ${message}${extra}\\n`);\n },\n };\n}\n"],"mappings":";AACO,SAAS,oBAAoB,SAAiB;AACnD,QAAM,SAAS,mBAAmB,OAAO;AACzC,SAAO;AAAA,IACL,KAAK,SAAiB,MAAsC;AAC1D,YAAM,QAAQ,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAClD,cAAQ,OAAO,MAAM,GAAG,MAAM,IAAI,OAAO,GAAG,KAAK;AAAA,CAAI;AAAA,IACvD;AAAA,IACA,KAAK,SAAiB,MAAsC;AAC1D,YAAM,QAAQ,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAClD,cAAQ,OAAO,MAAM,GAAG,MAAM,SAAS,OAAO,GAAG,KAAK;AAAA,CAAI;AAAA,IAC5D;AAAA,IACA,MAAM,SAAiB,MAAsC;AAC3D,YAAM,QAAQ,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAClD,cAAQ,OAAO,MAAM,GAAG,MAAM,UAAU,OAAO,GAAG,KAAK;AAAA,CAAI;AAAA,IAC7D;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,51 @@
1
+ // src/harness/types.ts
2
+ function defineTool(name, description, schema, handler, options) {
3
+ return {
4
+ name,
5
+ description,
6
+ schema,
7
+ handler,
8
+ annotations: options?.annotations
9
+ };
10
+ }
11
+
12
+ // src/harness/claude-code/index.ts
13
+ import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
14
+ var ClaudeCodeHarness = class {
15
+ async *executeQuery(opts) {
16
+ const sdkEvents = query({
17
+ prompt: opts.prompt,
18
+ options: {
19
+ ...opts.options,
20
+ ...opts.resume ? { resume: opts.resume } : {},
21
+ ...opts.options.abortController ? { abortController: opts.options.abortController } : {}
22
+ }
23
+ });
24
+ for await (const event of sdkEvents) {
25
+ yield event;
26
+ }
27
+ }
28
+ createMcpServer(config) {
29
+ const sdkTools = config.tools.map(
30
+ (t) => tool(
31
+ t.name,
32
+ t.description,
33
+ t.schema,
34
+ t.handler,
35
+ t.annotations ? { annotations: t.annotations } : void 0
36
+ )
37
+ );
38
+ return createSdkMcpServer({ name: config.name, tools: sdkTools });
39
+ }
40
+ };
41
+
42
+ // src/harness/index.ts
43
+ function createHarness() {
44
+ return new ClaudeCodeHarness();
45
+ }
46
+
47
+ export {
48
+ defineTool,
49
+ createHarness
50
+ };
51
+ //# sourceMappingURL=chunk-KNBG2634.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/harness/types.ts","../src/harness/claude-code/index.ts","../src/harness/index.ts"],"sourcesContent":["/**\n * Harness-neutral types for agent query execution.\n *\n * These types abstract the underlying agent SDK so that the runner,\n * execution, and tool layers never reference SDK-specific imports\n * directly. The only place that should import from\n * `@anthropic-ai/claude-agent-sdk` is `harness/claude-code/`.\n */\n\nimport type { z } from \"zod\";\n\n// ── Events emitted by the harness during a query ────────────────────────\n\nexport interface HarnessSystemInitEvent {\n type: \"system\";\n subtype: \"init\";\n session_id?: string;\n model: string;\n}\n\nexport interface HarnessCompactBoundaryEvent {\n type: \"system\";\n subtype: \"compact_boundary\";\n compact_metadata: { trigger: \"manual\" | \"auto\"; pre_tokens: number };\n}\n\nexport interface HarnessTaskStartedEvent {\n type: \"system\";\n subtype: \"task_started\";\n task_id: string;\n description: string;\n}\n\nexport interface HarnessTaskProgressEvent {\n type: \"system\";\n subtype: \"task_progress\";\n task_id: string;\n description: string;\n usage?: { tool_uses: number; duration_ms: number };\n}\n\nexport type HarnessSystemEvent =\n | HarnessSystemInitEvent\n | HarnessCompactBoundaryEvent\n | HarnessTaskStartedEvent\n | HarnessTaskProgressEvent;\n\nexport interface HarnessContentBlock {\n type: string;\n text?: string;\n name?: string;\n input?: unknown;\n id?: string;\n}\n\nexport interface HarnessAssistantEvent {\n type: \"assistant\";\n message: {\n role: \"assistant\";\n content: HarnessContentBlock[];\n usage?: {\n input_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n };\n}\n\nexport interface HarnessResultSuccessEvent {\n type: \"result\";\n subtype: \"success\";\n result: string;\n total_cost_usd: number;\n modelUsage?: Record<string, unknown>;\n sessionId?: string;\n}\n\nexport interface HarnessResultErrorEvent {\n type: \"result\";\n subtype: \"error\";\n errors: string[];\n sessionId?: string;\n}\n\nexport type HarnessResultEvent = HarnessResultSuccessEvent | HarnessResultErrorEvent;\n\nexport interface HarnessRateLimitEvent {\n type: \"rate_limit_event\";\n rate_limit_info: {\n status: string;\n rateLimitType?: string;\n utilization?: number;\n resetsAt?: unknown;\n };\n}\n\nexport interface HarnessToolProgressEvent {\n type: \"tool_progress\";\n tool_name?: string;\n elapsed_time_seconds?: number;\n}\n\nexport type HarnessEvent =\n | HarnessSystemEvent\n | HarnessAssistantEvent\n | HarnessResultEvent\n | HarnessRateLimitEvent\n | HarnessToolProgressEvent;\n\n// ── User message fed into a query ───────────────────────────────────────\n\nexport interface HarnessUserMessage {\n type: \"user\";\n session_id: string;\n message: { role: \"user\"; content: string | unknown[] };\n parent_tool_use_id: null;\n}\n\n// ── Tool definition (harness-neutral) ───────────────────────────────────\n\nexport interface HarnessToolAnnotations {\n readOnlyHint?: boolean;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- generic tool handler */\nexport interface HarnessToolDefinition {\n name: string;\n description: string;\n schema: z.ZodRawShape;\n handler: (\n input: any,\n ) => Promise<{ content: { type: string; text?: string; data?: string; mimeType?: string }[] }>;\n annotations?: HarnessToolAnnotations;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n// ── Hook types ──────────────────────────────────────────────────────────\n\nexport interface HarnessHookInput {\n hook_event_name: string;\n tool_name: string;\n tool_response: unknown;\n}\n\nexport interface HarnessHookOutput {\n continue: boolean;\n}\n\nexport type HarnessPostToolUseHook = (input: HarnessHookInput) => Promise<HarnessHookOutput>;\n\n// ── MCP server handle (opaque to the runner) ────────────────────────────\n\n/** Opaque handle returned by `AgentHarness.createMcpServer()`. */\nexport type HarnessMcpServer = unknown;\n\n// ── Query options ───────────────────────────────────────────────────────\n\nexport interface HarnessQueryOptions {\n model: string;\n systemPrompt: unknown;\n cwd: string;\n permissionMode: \"plan\" | \"bypassPermissions\";\n allowDangerouslySkipPermissions: boolean;\n tools: { type: \"preset\"; preset: \"claude_code\" };\n mcpServers: Record<string, HarnessMcpServer>;\n settingSources?: (\"user\" | \"project\" | \"local\")[];\n sandbox?: { enabled: boolean };\n maxTurns?: number;\n maxBudgetUsd?: number;\n effort?: string;\n thinking?: unknown;\n betas?: unknown;\n disallowedTools?: string[];\n abortController?: AbortController;\n enableFileCheckpointing?: boolean;\n canUseTool?: (\n toolName: string,\n input: Record<string, unknown>,\n ) => Promise<\n | { behavior: \"allow\"; updatedInput?: Record<string, unknown> }\n | { behavior: \"deny\"; message: string }\n >;\n hooks?: Record<string, { hooks: HarnessPostToolUseHook[]; timeout: number }[]>;\n resume?: string;\n stderr?: (data: string) => void;\n}\n\n// ── Tool definition helper ──────────────────────────────────────────────\n\n/**\n * Construct a `HarnessToolDefinition` with the same signature as the SDK's\n * `tool()` helper, but without importing the SDK. The harness implementation\n * converts these into SDK-native tools when `createMcpServer()` is called.\n */\nexport function defineTool<T extends z.ZodRawShape>(\n name: string,\n description: string,\n schema: T,\n handler: (\n input: z.infer<z.ZodObject<T>>,\n ) => Promise<{ content: { type: string; text?: string; data?: string; mimeType?: string }[] }>,\n options?: { annotations?: HarnessToolAnnotations },\n): HarnessToolDefinition {\n return {\n name,\n description,\n schema,\n handler: handler as HarnessToolDefinition[\"handler\"],\n annotations: options?.annotations,\n };\n}\n\n// ── AgentHarness interface ──────────────────────────────────────────────\n\nexport interface AgentHarness {\n /** Start a streaming query and return an async generator of events. */\n executeQuery(options: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void>;\n\n /** Wrap an array of harness-neutral tool definitions into an MCP server. */\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer;\n}\n","/**\n * ClaudeCodeHarness — wraps `@anthropic-ai/claude-agent-sdk` behind the\n * generic `AgentHarness` interface.\n *\n * This is the ONLY module that should import from the SDK.\n */\n\nimport { query, tool, createSdkMcpServer } from \"@anthropic-ai/claude-agent-sdk\";\nimport type {\n AgentHarness,\n HarnessEvent,\n HarnessQueryOptions,\n HarnessToolDefinition,\n HarnessMcpServer,\n HarnessUserMessage,\n} from \"../types.js\";\n\nexport class ClaudeCodeHarness implements AgentHarness {\n async *executeQuery(opts: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void> {\n const sdkEvents = query({\n prompt: opts.prompt as Parameters<typeof query>[0][\"prompt\"],\n options: {\n ...(opts.options as Parameters<typeof query>[0][\"options\"]),\n ...(opts.resume ? { resume: opts.resume } : {}),\n ...(opts.options.abortController ? { abortController: opts.options.abortController } : {}),\n },\n });\n\n for await (const event of sdkEvents) {\n yield event as unknown as HarnessEvent;\n }\n }\n\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer {\n const sdkTools = config.tools.map((t) =>\n tool(\n t.name,\n t.description,\n t.schema,\n t.handler as Parameters<typeof tool>[3],\n t.annotations ? { annotations: t.annotations } : undefined,\n ),\n );\n return createSdkMcpServer({ name: config.name, tools: sdkTools });\n }\n}\n","export { defineTool } from \"./types.js\";\n\nexport type {\n AgentHarness,\n HarnessEvent,\n HarnessSystemEvent,\n HarnessSystemInitEvent,\n HarnessCompactBoundaryEvent,\n HarnessTaskStartedEvent,\n HarnessTaskProgressEvent,\n HarnessAssistantEvent,\n HarnessContentBlock,\n HarnessResultEvent,\n HarnessResultSuccessEvent,\n HarnessResultErrorEvent,\n HarnessRateLimitEvent,\n HarnessToolProgressEvent,\n HarnessUserMessage,\n HarnessToolDefinition,\n HarnessToolAnnotations,\n HarnessMcpServer,\n HarnessQueryOptions,\n HarnessHookInput,\n HarnessHookOutput,\n HarnessPostToolUseHook,\n} from \"./types.js\";\n\nexport { ClaudeCodeHarness } from \"./claude-code/index.js\";\n\nimport { ClaudeCodeHarness } from \"./claude-code/index.js\";\nimport type { AgentHarness } from \"./types.js\";\n\nexport function createHarness(): AgentHarness {\n return new ClaudeCodeHarness();\n}\n"],"mappings":";AAkMO,SAAS,WACd,MACA,aACA,QACA,SAGA,SACuB;AACvB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,SAAS;AAAA,EACxB;AACF;;;AC3MA,SAAS,OAAO,MAAM,0BAA0B;AAUzC,IAAM,oBAAN,MAAgD;AAAA,EACrD,OAAO,aAAa,MAImB;AACrC,UAAM,YAAY,MAAM;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,SAAS;AAAA,QACP,GAAI,KAAK;AAAA,QACT,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QAC7C,GAAI,KAAK,QAAQ,kBAAkB,EAAE,iBAAiB,KAAK,QAAQ,gBAAgB,IAAI,CAAC;AAAA,MAC1F;AAAA,IACF,CAAC;AAED,qBAAiB,SAAS,WAAW;AACnC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,gBAAgB,QAA4E;AAC1F,UAAM,WAAW,OAAO,MAAM;AAAA,MAAI,CAAC,MACjC;AAAA,QACE,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,IAAI;AAAA,MACnD;AAAA,IACF;AACA,WAAO,mBAAmB,EAAE,MAAM,OAAO,MAAM,OAAO,SAAS,CAAC;AAAA,EAClE;AACF;;;ACjBO,SAAS,gBAA8B;AAC5C,SAAO,IAAI,kBAAkB;AAC/B;","names":[]}
@@ -1,3 +1,11 @@
1
+ import {
2
+ createHarness,
3
+ defineTool
4
+ } from "./chunk-KNBG2634.js";
5
+ import {
6
+ createServiceLogger
7
+ } from "./chunk-CYZPFJGN.js";
8
+
1
9
  // src/connection/agent-connection.ts
2
10
  import { io } from "socket.io-client";
3
11
  var EVENT_BATCH_MS = 500;
@@ -133,15 +141,7 @@ var AgentConnection = class {
133
141
  });
134
142
  this.socket.io.on("reconnect", () => {
135
143
  process.stderr.write("[conveyor-agent] Reconnected\n");
136
- void this.call("connectAgent", { sessionId: this.config.sessionId }).catch(() => {
137
- });
138
- if (this.lastEmittedStatus) {
139
- void this.call("reportAgentStatus", {
140
- sessionId: this.config.sessionId,
141
- status: this.lastEmittedStatus
142
- }).catch(() => {
143
- });
144
- }
144
+ void this.reconnectToSession();
145
145
  });
146
146
  this.socket.io.on("reconnect_attempt", () => {
147
147
  });
@@ -156,6 +156,53 @@ var AgentConnection = class {
156
156
  this.socket = null;
157
157
  }
158
158
  }
159
+ // ── Reconnect with retry ────────────────────────────────────────────
160
+ async reconnectToSession() {
161
+ const maxRetries = 5;
162
+ const baseDelayMs = 2e3;
163
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
164
+ try {
165
+ const { pendingMessages } = await this.call("connectAgent", {
166
+ sessionId: this.config.sessionId
167
+ });
168
+ this.drainPendingMessages(pendingMessages);
169
+ process.stderr.write("[conveyor-agent] Reconnected to session successfully\n");
170
+ if (this.lastEmittedStatus) {
171
+ void this.call("reportAgentStatus", {
172
+ sessionId: this.config.sessionId,
173
+ status: this.lastEmittedStatus
174
+ }).catch(() => {
175
+ });
176
+ }
177
+ return;
178
+ } catch (err) {
179
+ const errMsg = err instanceof Error ? err.message : String(err);
180
+ const delayMs = baseDelayMs * 2 ** (attempt - 1);
181
+ process.stderr.write(
182
+ `[conveyor-agent] connectAgent failed (attempt ${attempt}/${maxRetries}): ${errMsg} \u2014 retrying in ${delayMs / 1e3}s
183
+ `
184
+ );
185
+ await new Promise((resolve2) => {
186
+ setTimeout(resolve2, delayMs);
187
+ });
188
+ }
189
+ }
190
+ process.stderr.write(
191
+ `[conveyor-agent] Failed to reconnect to session after ${maxRetries} attempts, exiting
192
+ `
193
+ );
194
+ process.exit(1);
195
+ }
196
+ drainPendingMessages(messages) {
197
+ for (const msg of messages) {
198
+ if (!msg.content) continue;
199
+ if (this.messageCallback) {
200
+ this.messageCallback({ content: msg.content, userId: msg.userId });
201
+ } else {
202
+ this.earlyMessages.push({ content: msg.content, userId: msg.userId });
203
+ }
204
+ }
205
+ }
159
206
  // ── Callback registration with early-buffer draining ───────────────
160
207
  onMessage(callback) {
161
208
  this.messageCallback = callback;
@@ -543,28 +590,6 @@ var Lifecycle = class {
543
590
  }
544
591
  };
545
592
 
546
- // src/utils/logger.ts
547
- function createServiceLogger(service) {
548
- const prefix = `[conveyor-agent:${service}]`;
549
- return {
550
- info(message, data) {
551
- const extra = data ? ` ${JSON.stringify(data)}` : "";
552
- process.stderr.write(`${prefix} ${message}${extra}
553
- `);
554
- },
555
- warn(message, data) {
556
- const extra = data ? ` ${JSON.stringify(data)}` : "";
557
- process.stderr.write(`${prefix} WARN ${message}${extra}
558
- `);
559
- },
560
- error(message, data) {
561
- const extra = data ? ` ${JSON.stringify(data)}` : "";
562
- process.stderr.write(`${prefix} ERROR ${message}${extra}
563
- `);
564
- }
565
- };
566
- }
567
-
568
593
  // src/runner/git-utils.ts
569
594
  import { execSync } from "child_process";
570
595
  function hasUncommittedChanges(cwd) {
@@ -772,52 +797,6 @@ var PlanSync = class {
772
797
  }
773
798
  };
774
799
 
775
- // src/harness/types.ts
776
- function defineTool(name, description, schema, handler, options) {
777
- return {
778
- name,
779
- description,
780
- schema,
781
- handler,
782
- annotations: options?.annotations
783
- };
784
- }
785
-
786
- // src/harness/claude-code/index.ts
787
- import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
788
- var ClaudeCodeHarness = class {
789
- async *executeQuery(opts) {
790
- const sdkEvents = query({
791
- prompt: opts.prompt,
792
- options: {
793
- ...opts.options,
794
- ...opts.resume ? { resume: opts.resume } : {},
795
- ...opts.options.abortController ? { abortController: opts.options.abortController } : {}
796
- }
797
- });
798
- for await (const event of sdkEvents) {
799
- yield event;
800
- }
801
- }
802
- createMcpServer(config) {
803
- const sdkTools = config.tools.map(
804
- (t) => tool(
805
- t.name,
806
- t.description,
807
- t.schema,
808
- t.handler,
809
- t.annotations ? { annotations: t.annotations } : void 0
810
- )
811
- );
812
- return createSdkMcpServer({ name: config.name, tools: sdkTools });
813
- }
814
- };
815
-
816
- // src/harness/index.ts
817
- function createHarness() {
818
- return new ClaudeCodeHarness();
819
- }
820
-
821
800
  // src/execution/pack-runner-prompt.ts
822
801
  function findLastAgentMessageIndex(history) {
823
802
  for (let i = history.length - 1; i >= 0; i--) {
@@ -1209,8 +1188,7 @@ function buildAutoPrompt(context) {
1209
1188
  `You are in Auto mode \u2014 operating autonomously through planning \u2192 building \u2192 PR.`,
1210
1189
  ``,
1211
1190
  `### Phase 1: Discovery & Planning (current)`,
1212
- `- You have read-only codebase access (can read files, run git commands, search code)`,
1213
- `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1191
+ `- You are in the SDK's plan mode \u2014 read-only access is enforced automatically`,
1214
1192
  `- You have MCP tools for task properties: update_task, update_task_properties`,
1215
1193
  ``,
1216
1194
  `### Required before transitioning:`,
@@ -2334,7 +2312,16 @@ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
2334
2312
  }
2335
2313
  }
2336
2314
  if (hasUnpushedCommits(cwd)) {
2337
- const pushSuccess = await pushToOrigin(cwd);
2315
+ const pushSuccess = await pushToOrigin(cwd, async () => {
2316
+ try {
2317
+ const result2 = await connection.call("refreshGithubToken", {
2318
+ sessionId: connection.sessionId
2319
+ });
2320
+ return result2.token;
2321
+ } catch {
2322
+ return void 0;
2323
+ }
2324
+ });
2338
2325
  if (pushSuccess) {
2339
2326
  connection.sendEvent({
2340
2327
  type: "message",
@@ -2347,6 +2334,7 @@ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
2347
2334
  }
2348
2335
  }
2349
2336
  const result = await connection.call("createPullRequest", {
2337
+ sessionId: connection.sessionId,
2350
2338
  title,
2351
2339
  body,
2352
2340
  head: branch,
@@ -4696,18 +4684,12 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
4696
4684
  if (hasExitedPlanMode) {
4697
4685
  return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
4698
4686
  }
4699
- if (PM_PLAN_FILE_TOOLS.has(toolName)) {
4700
- if (isPlanFile(input)) {
4701
- return { behavior: "allow", updatedInput: input };
4702
- }
4703
- return {
4704
- behavior: "deny",
4705
- message: "You are in auto plan mode. File writes are restricted to plan files. Call ExitPlanMode when your plan is ready to start building."
4706
- };
4707
- }
4708
4687
  return { behavior: "allow", updatedInput: input };
4709
4688
  }
4710
4689
  async function handleExitPlanMode(host, input) {
4690
+ if (host.hasExitedPlanMode) {
4691
+ return { behavior: "allow", updatedInput: input };
4692
+ }
4711
4693
  try {
4712
4694
  host.syncPlanFile();
4713
4695
  const taskProps = await host.connection.getTaskProperties();
@@ -5274,6 +5256,8 @@ var QueryBridge = class {
5274
5256
  const msg = err instanceof Error ? err.message : String(err);
5275
5257
  logger2.error("Query execution failed", { error: msg });
5276
5258
  this.connection.sendEvent({ type: "error", message: msg });
5259
+ } finally {
5260
+ this.mode.pendingModeRestart = false;
5277
5261
  }
5278
5262
  }
5279
5263
  // ── QueryHost construction ──────────────────────────────────────────
@@ -5343,6 +5327,7 @@ var SessionRunner = class _SessionRunner {
5343
5327
  callbacks;
5344
5328
  _state = "connecting";
5345
5329
  stopped = false;
5330
+ hasCompleted = false;
5346
5331
  interrupted = false;
5347
5332
  taskContext = null;
5348
5333
  fullContext = null;
@@ -5354,7 +5339,7 @@ var SessionRunner = class _SessionRunner {
5354
5339
  this.config = config;
5355
5340
  this.callbacks = callbacks;
5356
5341
  this.connection = new AgentConnection(config.connection);
5357
- const initialMode = config.agentMode ?? (config.runnerMode === "pm" ? "discovery" : "building");
5342
+ const initialMode = config.agentMode ?? (config.runnerMode === "pm" ? config.isAuto ? "auto" : "discovery" : "building");
5358
5343
  this.mode = new ModeController(initialMode, config.runnerMode, config.isAuto);
5359
5344
  const lifecycleConfig = { ...DEFAULT_LIFECYCLE_CONFIG, ...config.lifecycle };
5360
5345
  this.lifecycle = new Lifecycle(lifecycleConfig, {
@@ -5430,6 +5415,9 @@ var SessionRunner = class _SessionRunner {
5430
5415
  this.queryBridge = this.createQueryBridge();
5431
5416
  this.logInitialization();
5432
5417
  await this.executeInitialMode();
5418
+ if (!this.stopped && this._state !== "error") {
5419
+ this.hasCompleted = true;
5420
+ }
5433
5421
  if (!this.stopped && this.pendingMessages.length === 0) {
5434
5422
  await this.maybeSendPRNudge();
5435
5423
  }
@@ -5459,6 +5447,12 @@ var SessionRunner = class _SessionRunner {
5459
5447
  }
5460
5448
  break;
5461
5449
  }
5450
+ if (this.hasCompleted && msg.userId === "system") {
5451
+ continue;
5452
+ }
5453
+ if (msg.userId !== "system") {
5454
+ this.hasCompleted = false;
5455
+ }
5462
5456
  await this.setState("running");
5463
5457
  this.interrupted = false;
5464
5458
  await this.callbacks.onEvent({
@@ -5472,6 +5466,7 @@ var SessionRunner = class _SessionRunner {
5472
5466
  if (!this.stopped && this.pendingMessages.length === 0) {
5473
5467
  await this.maybeSendPRNudge();
5474
5468
  }
5469
+ this.hasCompleted = true;
5475
5470
  if (!this.stopped) await this.setState("idle");
5476
5471
  } else if (this._state === "error") {
5477
5472
  await this.setState("idle");
@@ -5634,7 +5629,12 @@ var SessionRunner = class _SessionRunner {
5634
5629
  baseBranch: ctx.baseBranch ?? "",
5635
5630
  githubPRUrl: ctx.githubPRUrl,
5636
5631
  claudeSessionId: ctx.claudeSessionId ?? null,
5637
- isParentTask: !!ctx.parentTaskId
5632
+ isParentTask: !!ctx.parentTaskId,
5633
+ storyPoints: ctx.storyPoints ?? void 0,
5634
+ projectAgents: ctx.projectAgents ?? void 0,
5635
+ projectTags: ctx.projectTags ?? void 0,
5636
+ taskTagIds: ctx.taskTagIds ?? void 0,
5637
+ projectObjectives: ctx.projectObjectives ?? void 0
5638
5638
  };
5639
5639
  }
5640
5640
  createQueryBridge() {
@@ -5833,6 +5833,9 @@ var ProjectConnection = class {
5833
5833
  onRestartStartCommand(callback) {
5834
5834
  this.requireSocket().on("projectRunner:restartStartCommand", callback);
5835
5835
  }
5836
+ onAuditTags(callback) {
5837
+ this.requireSocket().on("projectRunner:auditTags", callback);
5838
+ }
5836
5839
  // ── Outgoing helpers ───────────────────────────────────────────────────
5837
5840
  sendHeartbeat() {
5838
5841
  void this.call("projectRunnerHeartbeat", { projectId: this.config.projectId }).catch(() => {
@@ -6342,21 +6345,24 @@ function buildChatQueryOptions(agentCtx, projectDir, connection) {
6342
6345
  tools: buildProjectTools(connection)
6343
6346
  });
6344
6347
  return {
6345
- model,
6346
- systemPrompt: {
6347
- type: "preset",
6348
- preset: "claude_code",
6349
- append: buildSystemPrompt2(projectDir, agentCtx)
6348
+ options: {
6349
+ model,
6350
+ systemPrompt: {
6351
+ type: "preset",
6352
+ preset: "claude_code",
6353
+ append: buildSystemPrompt2(projectDir, agentCtx)
6354
+ },
6355
+ cwd: projectDir,
6356
+ permissionMode: "bypassPermissions",
6357
+ allowDangerouslySkipPermissions: true,
6358
+ tools: { type: "preset", preset: "claude_code" },
6359
+ mcpServers: { conveyor: mcpServer },
6360
+ maxTurns: settings.maxTurns ?? 30,
6361
+ maxBudgetUsd: settings.maxBudgetUsd ?? 50,
6362
+ effort: settings.effort,
6363
+ thinking: settings.thinking
6350
6364
  },
6351
- cwd: projectDir,
6352
- permissionMode: "bypassPermissions",
6353
- allowDangerouslySkipPermissions: true,
6354
- tools: { type: "preset", preset: "claude_code" },
6355
- mcpServers: { conveyor: mcpServer },
6356
- maxTurns: settings.maxTurns ?? 30,
6357
- maxBudgetUsd: settings.maxBudgetUsd ?? 50,
6358
- effort: settings.effort,
6359
- thinking: settings.thinking
6365
+ harness
6360
6366
  };
6361
6367
  }
6362
6368
  function processContentBlock(block, responseParts, turnToolCalls) {
@@ -6405,10 +6411,9 @@ function emitResultCost(event, connection) {
6405
6411
  }
6406
6412
  async function runChatQuery(message, connection, projectDir, sessionId) {
6407
6413
  const { agentCtx, chatHistory } = await fetchContext(connection, message.chatId);
6408
- const options = buildChatQueryOptions(agentCtx, projectDir, connection);
6414
+ const { options, harness } = buildChatQueryOptions(agentCtx, projectDir, connection);
6409
6415
  const prompt = buildPrompt(message, chatHistory);
6410
6416
  connection.emitStatus("busy");
6411
- const harness = createHarness();
6412
6417
  const events = harness.executeQuery({
6413
6418
  prompt,
6414
6419
  options,
@@ -6433,7 +6438,8 @@ async function runChatQuery(message, connection, projectDir, sessionId) {
6433
6438
  if (responseText) {
6434
6439
  await connection.call("postProjectAgentMessage", {
6435
6440
  projectId: connection.projectId,
6436
- content: responseText
6441
+ content: responseText,
6442
+ chatId: message.chatId
6437
6443
  });
6438
6444
  }
6439
6445
  return resultSessionId;
@@ -6448,7 +6454,8 @@ async function handleProjectChatMessage(message, connection, projectDir, session
6448
6454
  try {
6449
6455
  await connection.call("postProjectAgentMessage", {
6450
6456
  projectId: connection.projectId,
6451
- content: "I encountered an error processing your message. Please try again."
6457
+ content: "I encountered an error processing your message. Please try again.",
6458
+ chatId: message.chatId
6452
6459
  });
6453
6460
  } catch {
6454
6461
  }
@@ -6580,7 +6587,7 @@ var ProjectRunner = class {
6580
6587
  await this.connection.connect();
6581
6588
  const registration = await this.connection.call("registerProjectAgent", {
6582
6589
  projectId: this.connection.projectId,
6583
- capabilities: ["task", "pm", "code-review"]
6590
+ capabilities: ["task", "pm", "code-review", "audit"]
6584
6591
  });
6585
6592
  this.branchSwitchCommand = registration.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
6586
6593
  logger6.info("Registered as project agent", { agentName: registration.agentName });
@@ -6653,7 +6660,7 @@ var ProjectRunner = class {
6653
6660
  }
6654
6661
  // ── Event wiring ───────────────────────────────────────────────────────
6655
6662
  wireEventHandlers() {
6656
- this.connection.onTaskAssignment((assignment) => this.handleAssignment(assignment));
6663
+ this.connection.onTaskAssignment((assignment) => void this.handleAssignment(assignment));
6657
6664
  this.connection.onStopTask((data) => this.handleStopTask(data.taskId));
6658
6665
  this.connection.onShutdown(() => void this.stop());
6659
6666
  this.connection.onChatMessage((msg) => {
@@ -6665,6 +6672,7 @@ var ProjectRunner = class {
6665
6672
  }
6666
6673
  );
6667
6674
  });
6675
+ this.connection.onAuditTags((request) => void this.handleAuditTags(request));
6668
6676
  this.connection.onSwitchBranch((data, cb) => void this.handleSwitchBranch(data, cb));
6669
6677
  this.connection.onSyncEnvironment((cb) => void this.handleSyncEnvironment(cb));
6670
6678
  this.connection.onRestartStartCommand((cb) => {
@@ -6677,7 +6685,7 @@ var ProjectRunner = class {
6677
6685
  try {
6678
6686
  await this.connection.call("registerProjectAgent", {
6679
6687
  projectId: this.connection.projectId,
6680
- capabilities: ["task", "pm", "code-review"]
6688
+ capabilities: ["task", "pm", "code-review", "audit"]
6681
6689
  });
6682
6690
  this.connection.emitStatus(this.activeAgents.size > 0 ? "busy" : "idle");
6683
6691
  logger6.info("Re-registered after reconnect");
@@ -6686,14 +6694,66 @@ var ProjectRunner = class {
6686
6694
  logger6.error("Failed to re-register after reconnect", { error: msg });
6687
6695
  }
6688
6696
  }
6697
+ // ── Tag audit ──────────────────────────────────────────────────────────
6698
+ async handleAuditTags(request) {
6699
+ this.connection.emitStatus("busy");
6700
+ try {
6701
+ const { handleTagAudit } = await import("./tag-audit-handler-4RRGIHVB.js");
6702
+ await handleTagAudit(request, this.connection, this.projectDir);
6703
+ } catch (error) {
6704
+ const msg = error instanceof Error ? error.message : String(error);
6705
+ logger6.error("Tag audit failed", { error: msg, requestId: request.requestId });
6706
+ try {
6707
+ await this.connection.call("reportTagAuditResult", {
6708
+ projectId: this.connection.projectId,
6709
+ requestId: request.requestId,
6710
+ recommendations: [],
6711
+ summary: `Audit failed: ${msg}`,
6712
+ complete: true
6713
+ });
6714
+ } catch {
6715
+ }
6716
+ } finally {
6717
+ this.connection.emitStatus("idle");
6718
+ }
6719
+ }
6689
6720
  // ── Task management ────────────────────────────────────────────────────
6690
- handleAssignment(assignment) {
6721
+ async killAgent(agent, taskId) {
6722
+ const shortId = taskId.slice(0, 8);
6723
+ if (agent.process.exitCode !== null) {
6724
+ logger6.info("Agent process already exited", { taskId: shortId });
6725
+ return;
6726
+ }
6727
+ logger6.info("Killing agent process", { taskId: shortId });
6728
+ agent.process.kill("SIGTERM");
6729
+ await new Promise((resolve2) => {
6730
+ const timer = setTimeout(() => {
6731
+ if (agent.process.exitCode === null) {
6732
+ logger6.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
6733
+ agent.process.kill("SIGKILL");
6734
+ }
6735
+ resolve2();
6736
+ }, STOP_TIMEOUT_MS);
6737
+ agent.process.on("exit", () => {
6738
+ clearTimeout(timer);
6739
+ resolve2();
6740
+ });
6741
+ });
6742
+ }
6743
+ // oxlint-disable-next-line max-lines-per-function -- re-assignment logic requires sequential checks
6744
+ async handleAssignment(assignment) {
6691
6745
  const { taskId, mode } = assignment;
6692
6746
  const shortId = taskId.slice(0, 8);
6693
6747
  const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
6694
- if (this.activeAgents.has(agentKey)) {
6695
- logger6.info("Task already running, skipping", { taskId: shortId });
6696
- return;
6748
+ const existing = this.activeAgents.get(agentKey);
6749
+ if (existing) {
6750
+ if (existing.process.exitCode === null) {
6751
+ logger6.info("Re-assignment received, killing existing agent", { taskId: shortId });
6752
+ await this.killAgent(existing, taskId);
6753
+ } else {
6754
+ logger6.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
6755
+ }
6756
+ this.activeAgents.delete(agentKey);
6697
6757
  }
6698
6758
  if (this.activeAgents.size >= MAX_CONCURRENT) {
6699
6759
  logger6.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
@@ -6735,16 +6795,11 @@ var ProjectRunner = class {
6735
6795
  }
6736
6796
  }
6737
6797
  handleStopTask(taskId) {
6738
- let agent = this.activeAgents.get(taskId);
6739
- if (!agent) agent = this.activeAgents.get(`${taskId}:code-review`);
6798
+ const agentKey = this.activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
6799
+ const agent = this.activeAgents.get(agentKey);
6740
6800
  if (!agent) return;
6741
6801
  logger6.info("Stopping task", { taskId: taskId.slice(0, 8) });
6742
- agent.process.kill("SIGTERM");
6743
- const timer = setTimeout(() => {
6744
- if (this.activeAgents.has(taskId)) agent.process.kill("SIGKILL");
6745
- }, STOP_TIMEOUT_MS);
6746
- agent.process.on("exit", () => {
6747
- clearTimeout(timer);
6802
+ void this.killAgent(agent, taskId).then(() => {
6748
6803
  if (agent.usesWorktree) {
6749
6804
  try {
6750
6805
  removeWorktree(this.projectDir, taskId);
@@ -7013,7 +7068,6 @@ export {
7013
7068
  ModeController,
7014
7069
  DEFAULT_LIFECYCLE_CONFIG,
7015
7070
  Lifecycle,
7016
- createServiceLogger,
7017
7071
  hasUncommittedChanges,
7018
7072
  getCurrentBranch,
7019
7073
  hasUnpushedCommits,
@@ -7033,4 +7087,4 @@ export {
7033
7087
  removeWorktree,
7034
7088
  ProjectRunner
7035
7089
  };
7036
- //# sourceMappingURL=chunk-HAU4E7IA.js.map
7090
+ //# sourceMappingURL=chunk-L7ZH423N.js.map