vibe-coding-master 0.8.2 → 0.8.4

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/README.md CHANGED
@@ -195,7 +195,10 @@ Whenever Project Manager asks the user a question, it first uses
195
195
  `vcm-ask-user`. VCM records the wait and cancels any pending role dispatch in
196
196
  one state update. Stop Hook then ends the PM turn without route scanning. Only
197
197
  a new direct user message clears the wait, and the next role dispatch requires
198
- a fresh Workflow Review.
198
+ a fresh Workflow Review. PM treats text emitted while a Round continues as
199
+ intermediate output, not as delivered user communication. When the Round pauses
200
+ or completes, its Round Final Reply restates the complete user-facing result or
201
+ question without relying on earlier PM output.
199
202
 
200
203
  Typical flow:
201
204
 
@@ -506,14 +509,9 @@ provide the language server for each detected language: `rust-analyzer`,
506
509
  relationships. Accessors, backing fields, trait items, implementations, wrappers,
507
510
  and aliases are queried as separate symbols; a bounded source-text fallback may
508
511
  only locate candidates after the correct LSP query is demonstrably partial.
509
- Other roles use generated context and ordinary source reads.
510
- Harness Studio reports
511
- whether the server executable and plugin can run; the role Session performs the
512
- real workspace indexing and semantic query retries. VCM uses Claude Code progress
513
- hooks to detect a model response, tool call, subagent, or compaction that may be
514
- stalled. Detection only opens a warning; it does not interrupt the Session or
515
- change the Round. The user can ignore that warning or explicitly stop and resume
516
- the same Claude Session from the warning dialog.
512
+ Other roles use generated context and ordinary source reads. Harness Studio
513
+ reports whether the server executable and plugin can run; the role Session
514
+ performs the real workspace indexing and semantic query retries.
517
515
 
518
516
  Harness Engineer is task-scoped and runs from the active task worktree. The
519
517
  backend automatically starts a fresh Harness Engineer for each active task or
@@ -77,8 +77,7 @@ export function registerTaskRoutes(app, deps) {
77
77
  orchestration,
78
78
  roundState: displayedRoundState,
79
79
  workflowState,
80
- architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug),
81
- roleStallWarning: deps.roleStallDetector.getWarning(project.repoRoot, taskSlug)
80
+ architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug)
82
81
  };
83
82
  }
84
83
  catch (error) {
@@ -93,30 +92,12 @@ export function registerTaskRoutes(app, deps) {
93
92
  },
94
93
  roundState: degradedRoundState(taskSlug),
95
94
  workflowState: degradedWorkflowState(taskSlug),
96
- architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug),
97
- roleStallWarning: deps.roleStallDetector.getWarning(repoRoot, taskSlug)
95
+ architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug)
98
96
  };
99
97
  }
100
98
  throw error;
101
99
  }
102
100
  });
103
- app.post("/api/tasks/:taskSlug/role-stall/ignore", async (request) => {
104
- const project = await requireCurrentProject(deps.projectService);
105
- return deps.roleStallDetector.ignoreWarning(project.repoRoot, request.params.taskSlug, requireWarningId(request.body?.warningId));
106
- });
107
- app.post("/api/tasks/:taskSlug/role-stall/recover", async (request) => {
108
- const project = await requireCurrentProject(deps.projectService);
109
- const config = await deps.projectService.loadConfig(project.repoRoot);
110
- const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
111
- const taskRepoRoot = getTaskRuntimeRepoRoot(task);
112
- return deps.roleStallDetector.recoverWarning({
113
- repoRoot: project.repoRoot,
114
- taskRepoRoot,
115
- stateRoot: config.stateRoot,
116
- taskSlug: task.taskSlug,
117
- warningId: requireWarningId(request.body?.warningId)
118
- });
119
- });
120
101
  app.post("/api/tasks/:taskSlug/workflow-state", async (request) => {
121
102
  const project = await requireCurrentProject(deps.projectService);
122
103
  const config = await deps.projectService.loadConfig(project.repoRoot);
@@ -155,16 +136,6 @@ async function delayFlowPauseForTranslation(deps, input) {
155
136
  return input.roundState;
156
137
  }
157
138
  }
158
- function requireWarningId(value) {
159
- if (typeof value === "string" && value.trim()) {
160
- return value.trim();
161
- }
162
- throw new VcmError({
163
- code: "ROLE_STALL_WARNING_ID_REQUIRED",
164
- message: "A role stall warning id is required.",
165
- statusCode: 400
166
- });
167
- }
168
139
  async function requireCurrentProject(projectService) {
169
140
  const project = await projectService.getCurrentProject();
170
141
  if (!project) {
@@ -63,15 +63,8 @@ const VCM_HOOK_DEFINITIONS = [
63
63
  { eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
64
64
  { eventName: "PreToolUse", matcher: "Agent", command: VCM_SUBAGENT_GUARD_HOOK_COMMAND, timeout: 10 },
65
65
  { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
66
- { eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
67
- { eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
68
- { eventName: "PostToolUseFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
69
- { eventName: "PostToolBatch", command: VCM_HOOK_COMMAND, timeout: 5 },
70
- { eventName: "SubagentStart", command: VCM_HOOK_COMMAND, timeout: 5 },
71
- { eventName: "SubagentStop", command: VCM_HOOK_COMMAND, timeout: 5 },
72
66
  { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
73
67
  { eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
74
- { eventName: "PreCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
75
68
  { eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
76
69
  { eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
77
70
  ];
@@ -14,7 +14,6 @@ import { createCodexBridgeIntegrationService } from "./services/codex-bridge-int
14
14
  import { createAutoMemoryService } from "./services/auto-memory-service.js";
15
15
  import { createArchitectRestartService } from "./services/architect-restart-service.js";
16
16
  import { createRoleContextRestartService } from "./services/role-context-restart-service.js";
17
- import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
18
17
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
19
18
  import { createGateReviewService } from "./services/gate-review-service.js";
20
19
  import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
@@ -137,7 +136,6 @@ export async function createServer(deps, options = {}) {
137
136
  roundService: deps.roundService,
138
137
  taskWorkflowService: deps.taskWorkflowService,
139
138
  architectRestartService: deps.architectRestartService,
140
- roleStallDetector: deps.roleStallDetector,
141
139
  translationService: deps.translationService
142
140
  });
143
141
  registerSessionRoutes(app, {
@@ -245,7 +243,10 @@ export function createDefaultServerDeps(options = {}) {
245
243
  const runtime = createNodePtyTerminalRuntime({ fs });
246
244
  const registry = createSessionRegistry();
247
245
  const workflowControlService = createWorkflowControlService({ fs });
248
- const artifactService = createArtifactService(fs, { workflowControlService });
246
+ const artifactService = createArtifactService(fs, {
247
+ workflowControlService,
248
+ isRoleMemoryTurn: (taskRepoRoot, role) => autoMemoryService.isRoleMemoryTurn(taskRepoRoot, role)
249
+ });
249
250
  const projectService = createProjectService({ fs, git, appSettings });
250
251
  const taskService = createTaskService({ fs, git, artifactService, projectService });
251
252
  const taskWorkflowService = createTaskWorkflowService({ fs });
@@ -351,10 +352,6 @@ export function createDefaultServerDeps(options = {}) {
351
352
  sessionService
352
353
  });
353
354
  const transcripts = createClaudeTranscriptService();
354
- const roleStallDetector = createRoleStallDetectorService({
355
- sessionService,
356
- roundService
357
- });
358
355
  const translationService = createTranslationService({
359
356
  runtime,
360
357
  sessionRegistry: registry,
@@ -432,7 +429,6 @@ export function createDefaultServerDeps(options = {}) {
432
429
  translationWorkerService,
433
430
  architectRestartService,
434
431
  roleContextRestartService,
435
- roleStallDetector,
436
432
  workflowControlService
437
433
  });
438
434
  const runtimeCoordinator = createRuntimeCoordinatorService({
@@ -445,7 +441,6 @@ export function createDefaultServerDeps(options = {}) {
445
441
  harnessFeedbackService,
446
442
  autoMemoryService,
447
443
  roundService,
448
- roleStallDetector,
449
444
  gatewayService,
450
445
  async getStateRoot(repoRoot) {
451
446
  return (await projectService.loadConfig(repoRoot)).stateRoot;
@@ -487,7 +482,6 @@ export function createDefaultServerDeps(options = {}) {
487
482
  autoMemoryService,
488
483
  commandDispatcher,
489
484
  claudeHookService,
490
- roleStallDetector,
491
485
  messageService,
492
486
  taskLaunchService,
493
487
  gateReviewService,
@@ -219,6 +219,20 @@ export function createArtifactService(fs, deps = {}) {
219
219
  if (validation.errors.length > 0) {
220
220
  throw artifactRejected(input.kind, validation.errors);
221
221
  }
222
+ if ((input.kind === "docs-update-report" || input.kind === "docs-sync-report")
223
+ && deps.workflowControlService) {
224
+ // Memory-owned documentation work is independent of PM's active dispatch.
225
+ const memoryUpdate = input.kind === "docs-update-report"
226
+ && await deps.isRoleMemoryTurn?.(input.repoRoot, input.role);
227
+ if (!memoryUpdate) {
228
+ await deps.workflowControlService.assertDocsArtifactAllowed({
229
+ taskRepoRoot: input.repoRoot,
230
+ stateRoot: input.stateRoot ?? ".ai/vcm",
231
+ handoffDir: input.handoffDir,
232
+ taskSlug: input.taskSlug
233
+ }, input.kind, input.role);
234
+ }
235
+ }
222
236
  if (input.kind === "workflow-progress") {
223
237
  if (input.mode !== "final") {
224
238
  throw artifactRejected(input.kind, ["Workflow Progress must be submitted in final mode."]);
@@ -1,6 +1,7 @@
1
1
  import { isHarnessEngineerToolRoleName, isReviewerRoleName, isTranslatorToolRoleName, isVcmRoleName } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
3
  import { readTranscriptTurnEvidence } from "./claude-transcript-reply.js";
4
+ import { findPmUserQuestion } from "./pm-user-question.js";
4
5
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
5
6
  import { matchesRoleHookSession } from "./session-service.js";
6
7
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
@@ -257,12 +258,6 @@ export function createClaudeHookService(deps) {
257
258
  role: input.role,
258
259
  eventName
259
260
  });
260
- await deps.roleStallDetector?.recordHook({
261
- ...createStallContext(context),
262
- role: input.role,
263
- eventName,
264
- event: input.event
265
- });
266
261
  }
267
262
  if (session) {
268
263
  await deps.translationService.recordConversationBoundary({
@@ -344,7 +339,10 @@ export function createClaudeHookService(deps) {
344
339
  }
345
340
  else {
346
341
  const lastAssistantMessage = stringOrUndefined(input.event.last_assistant_message);
347
- if (options.allowBlock && lastAssistantMessage && asksUserQuestion(lastAssistantMessage)) {
342
+ const question = options.allowBlock && lastAssistantMessage
343
+ ? findPmUserQuestion(lastAssistantMessage)
344
+ : undefined;
345
+ if (question) {
348
346
  return {
349
347
  ok: true,
350
348
  eventName,
@@ -354,18 +352,12 @@ export function createClaudeHookService(deps) {
354
352
  dispatchedCount: 0,
355
353
  stopDecision: {
356
354
  behavior: "block",
357
- reason: "You asked the user a question without registering the wait. Use vcm-ask-user with the exact question, then ask that question and stop. Do not advance or route the workflow."
355
+ reason: `Possible unregistered user question: ${JSON.stringify(question.slice(0, 600))}. If you are asking the user, use vcm-ask-user with the exact question, then ask it and end the turn without advancing or routing the workflow. If this only reports or quotes another question, clarify that context and end normally; do not invent a user question or register a false wait.`
358
356
  }
359
357
  };
360
358
  }
361
359
  }
362
360
  }
363
- await deps.roleStallDetector?.recordHook({
364
- ...createStallContext(context),
365
- role: input.role,
366
- eventName,
367
- event: input.event
368
- });
369
361
  if (pmAwaitingUser) {
370
362
  return recordTurnEnd(input, context, eventName, {
371
363
  dispatchRouteFiles: false,
@@ -417,12 +409,6 @@ export function createClaudeHookService(deps) {
417
409
  if (memoryResult) {
418
410
  return memoryResult;
419
411
  }
420
- await deps.roleStallDetector?.recordHook({
421
- ...createStallContext(context),
422
- role: input.role,
423
- eventName,
424
- event: input.event
425
- });
426
412
  const routeDispatchInput = createRouteDispatchInput(input, context);
427
413
  const pending = await deps.messageService.listPendingRouteFiles(routeDispatchInput);
428
414
  const hasCompletionEvidence = pending.some((routeFile) => routeFile.fromRole === input.role);
@@ -496,14 +482,6 @@ export function createClaudeHookService(deps) {
496
482
  runtimeSessionId: stringOrUndefined(input.event.vcm_runtime_session_id),
497
483
  runtimeSessionToken: input.runtimeSessionToken
498
484
  });
499
- if (session) {
500
- await deps.roleStallDetector?.recordHook({
501
- ...createStallContext(context),
502
- role: input.role,
503
- eventName,
504
- event: input.event
505
- });
506
- }
507
485
  return {
508
486
  ok: true,
509
487
  eventName,
@@ -637,14 +615,6 @@ export function createClaudeHookService(deps) {
637
615
  eventName
638
616
  });
639
617
  }
640
- if (boundToTask) {
641
- await deps.roleStallDetector?.recordHook({
642
- ...createStallContext(context),
643
- role: input.role,
644
- eventName,
645
- event: input.event
646
- });
647
- }
648
618
  await deps.autoMemoryService.handleRoleHook({
649
619
  baseRepoRoot: context.project.repoRoot,
650
620
  taskRepoRoot: context.taskRepoRoot,
@@ -869,31 +839,6 @@ export function createClaudeHookService(deps) {
869
839
  taskSlug: context.taskSlug
870
840
  };
871
841
  }
872
- function createStallContext(context) {
873
- return {
874
- repoRoot: context.project.repoRoot,
875
- taskRepoRoot: context.taskRepoRoot,
876
- stateRoot: context.config.stateRoot,
877
- taskSlug: context.taskSlug
878
- };
879
- }
880
- async function processProgressHook(input, eventName) {
881
- const role = input.role;
882
- if (!isVcmRoleName(role)) {
883
- return completedHookResult(input, eventName);
884
- }
885
- const context = await getHookContext(input);
886
- if (!(await isCurrentRoleHook(context, input, eventName))) {
887
- return completedHookResult(input, eventName);
888
- }
889
- await deps.roleStallDetector?.recordHook({
890
- ...createStallContext(context),
891
- role,
892
- eventName,
893
- event: input.event
894
- });
895
- return completedHookResult(input, eventName);
896
- }
897
842
  function renderStopFailureRecoveryPrompt() {
898
843
  return [
899
844
  "[VCM Recovery]",
@@ -936,17 +881,6 @@ export function createClaudeHookService(deps) {
936
881
  });
937
882
  }
938
883
  const preferences = await deps.appSettings.getPreferences();
939
- if (deps.roleStallDetector) {
940
- const context = await getHookContext(input);
941
- if (await isCurrentRoleHook(context, input, "PermissionRequest")) {
942
- await deps.roleStallDetector.recordHook({
943
- ...createStallContext(context),
944
- role: input.role,
945
- eventName: "PermissionRequest",
946
- event: input.event
947
- });
948
- }
949
- }
950
884
  if (preferences.permissionRequestMode !== "allowAll") {
951
885
  return undefined;
952
886
  }
@@ -963,9 +897,6 @@ export function createClaudeHookService(deps) {
963
897
  async handleHook(input) {
964
898
  return withRoleHookLock(input, async () => {
965
899
  const eventName = parseHookEvent(input.event.hook_event_name);
966
- if (isProgressHook(eventName)) {
967
- return processProgressHook(input, eventName);
968
- }
969
900
  if (isTranslatorToolRoleName(input.role)) {
970
901
  return processTranslatorHook(input);
971
902
  }
@@ -1004,13 +935,6 @@ function parseHookEvent(value) {
1004
935
  if (value === "UserPromptSubmit"
1005
936
  || value === "Stop"
1006
937
  || value === "StopFailure"
1007
- || value === "PreToolUse"
1008
- || value === "PostToolUse"
1009
- || value === "PostToolUseFailure"
1010
- || value === "PostToolBatch"
1011
- || value === "SubagentStart"
1012
- || value === "SubagentStop"
1013
- || value === "PreCompact"
1014
938
  || value === "PostCompact") {
1015
939
  return value;
1016
940
  }
@@ -1021,15 +945,6 @@ function parseHookEvent(value) {
1021
945
  hint: "Use a Claude Code event installed by the VCM Harness."
1022
946
  });
1023
947
  }
1024
- function isProgressHook(eventName) {
1025
- return eventName === "PreToolUse"
1026
- || eventName === "PostToolUse"
1027
- || eventName === "PostToolUseFailure"
1028
- || eventName === "PostToolBatch"
1029
- || eventName === "SubagentStart"
1030
- || eventName === "SubagentStop"
1031
- || eventName === "PreCompact";
1032
- }
1033
948
  function parseBusinessHookEvent(value) {
1034
949
  const eventName = parseHookEvent(value);
1035
950
  if (eventName === "UserPromptSubmit"
@@ -1092,14 +1007,3 @@ function isDirectUserPrompt(prompt) {
1092
1007
  const normalized = prompt.trim();
1093
1008
  return normalized.length > 0 && !/^\[VCM(?:\s|\])/i.test(normalized);
1094
1009
  }
1095
- function asksUserQuestion(message) {
1096
- const withoutCodeBlocks = message.replace(/```[\s\S]*?```/g, "");
1097
- const lines = withoutCodeBlocks
1098
- .split(/\r?\n/)
1099
- .map((line) => line.trim())
1100
- .filter(Boolean);
1101
- return lines.some((line) => /[??]/.test(line)
1102
- || /^(?:please\s+(?:answer|choose|confirm|decide|provide|select|tell)|(?:can|could|do|does|is|are|should|will|would)\s+you\b)/i.test(line)
1103
- || /\b(?:please\s+(?:reply|respond|choose|confirm|decide|provide|select|tell|give)|i\s+need\s+your\s+(?:decision|choice|confirmation|answer)|let\s+me\s+know|your\s+(?:decision|choice|confirmation|answer)\s+is\s+required|waiting\s+for\s+your)\b/i.test(line)
1104
- || /(?:请(?:回答|回复|选择|确认|决定|提供|告知|给出|说明)|你(?:是否|能否|要不要|可否)|是否需要你|需要你(?:选择|确认|决定|提供|告知)|需要您的(?:选择|确认|决定|回复)|等待您的(?:选择|确认|决定|回复)|告诉我|告知我)/.test(line));
1105
- }
@@ -63,15 +63,8 @@ const VCM_HOOK_DEFINITIONS = [
63
63
  { eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
64
64
  { eventName: "PreToolUse", matcher: "Agent", command: VCM_SUBAGENT_GUARD_HOOK_COMMAND, timeout: 10 },
65
65
  { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
66
- { eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
67
- { eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
68
- { eventName: "PostToolUseFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
69
- { eventName: "PostToolBatch", command: VCM_HOOK_COMMAND, timeout: 5 },
70
- { eventName: "SubagentStart", command: VCM_HOOK_COMMAND, timeout: 5 },
71
- { eventName: "SubagentStop", command: VCM_HOOK_COMMAND, timeout: 5 },
72
66
  { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
73
67
  { eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
74
- { eventName: "PreCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
75
68
  { eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
76
69
  { eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
77
70
  ];
@@ -0,0 +1,62 @@
1
+ import remarkGfm from "remark-gfm";
2
+ import remarkParse from "remark-parse";
3
+ import { unified } from "unified";
4
+ const markdown = unified().use(remarkParse).use(remarkGfm).freeze();
5
+ // Match direct requests, not punctuation or requests mentioned inside a report.
6
+ const DIRECT_REQUESTS = [
7
+ /^(?:can|could|do|did|are|have|should|will|would)\s+you\b/i,
8
+ /^(?:should|shall|may|can|could)\s+(?:i|we)\b/i,
9
+ /^(?:which|what|how|where|when|who)\b[^.!?]*\b(?:you|your|(?:should|shall|can|may)\s+(?:i|we))\b/i,
10
+ /^(?:which|what)\s+(?:option|approach|behavior|behaviour|choice)\b[^.!?]*\bshould\s+be\b/i,
11
+ /^(?:please\s+)?(?:answer|reply|respond|choose|confirm|decide|provide|select|tell\s+me|let\s+me\s+know|give\s+(?:me|your))\b/i,
12
+ /^i\s+need\s+your\s+(?:decision|choice|confirmation|answer|approval)\b/i,
13
+ /^your\s+(?:decision|choice|confirmation|answer|approval)\s+is\s+required\b/i,
14
+ /^(?:(?:i\s+am|i'm|we\s+are|we're)\s+)?waiting\s+for\s+your\s+(?:decision|choice|confirmation|answer|reply|approval)\b/i,
15
+ /^(?:请(?:您|你)?(?:回答|回复|选择|确认|决定|提供|告知|给出|说明)|[你您](?:是否|能否|要不要|可否|希望|想要)|是否需要[你您]|需要[你您](?:选择|确认|决定|提供|告知)|(?:需要|等待)[你您]的(?:选择|确认|决定|回复)|告诉我|告知我)/
16
+ ];
17
+ export function findPmUserQuestion(message) {
18
+ return findQuestion(markdown.parse(message));
19
+ }
20
+ function findQuestion(node) {
21
+ // Quoted evidence and code are not PM addressing the user. Headings and table
22
+ // cells remain eligible: a real request does not stop being one when formatted.
23
+ if (["blockquote", "code", "html", "definition", "footnoteDefinition", "delete"].includes(node.type)) {
24
+ return undefined;
25
+ }
26
+ if (node.type === "paragraph" || node.type === "heading" || node.type === "tableCell") {
27
+ const prose = removeQuotedText(proseText(node)).replace(/\s+/g, " ");
28
+ for (const sentence of prose.split(/(?<=[.!?。!?;])\s*/)) {
29
+ const request = sentence.trim().replace(/^(?:(?:question(?:\s+for\s+you)?|your\s+decision|问题|请问)\s*[::]\s*|before\s+(?:i|we)\s+(?:continue|proceed),?\s+)/i, "");
30
+ if (DIRECT_REQUESTS.some((pattern) => pattern.test(request))) {
31
+ return sentence.trim();
32
+ }
33
+ }
34
+ return undefined;
35
+ }
36
+ if ("children" in node) {
37
+ for (const child of node.children) {
38
+ const question = findQuestion(child);
39
+ if (question)
40
+ return question;
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ function proseText(node) {
46
+ if (node.type === "text")
47
+ return node.value;
48
+ if (node.type === "break")
49
+ return "\n";
50
+ if (["inlineCode", "html", "image", "imageReference", "delete", "footnoteReference"].includes(node.type))
51
+ return " ";
52
+ if (node.type === "link") {
53
+ const label = node.children.map(proseText).join("");
54
+ return label === node.url || /^(?:https?:\/\/|www\.)/i.test(label) ? " " : label;
55
+ }
56
+ if ("children" in node)
57
+ return node.children.map(proseText).join("");
58
+ return " ";
59
+ }
60
+ function removeQuotedText(text) {
61
+ return text.replace(/"[^"]*"|“[^”]*”|‘[^’]*’|「[^」]*」|『[^』]*』|(?<!\w)'[^']+'(?!\w)/g, " ");
62
+ }
@@ -42,17 +42,9 @@ export function createRuntimeCoordinatorService(deps) {
42
42
  ]);
43
43
  const preferences = await deps.appSettings.getPreferences();
44
44
  if (!activeTask) {
45
- deps.roleStallDetector.clearProject(repoRoot);
46
45
  return { activeTask: null, gatewayStatus };
47
46
  }
48
47
  const taskRepoRoot = getTaskRuntimeRepoRoot(activeTask);
49
- const stateRoot = await deps.getStateRoot(repoRoot);
50
- await deps.roleStallDetector.reconcileTask({
51
- repoRoot,
52
- taskRepoRoot,
53
- stateRoot,
54
- taskSlug: activeTask.taskSlug
55
- });
56
48
  const harnessInitialized = await deps.harnessService.getHarnessStatus(taskRepoRoot)
57
49
  .then((status) => status.initialized)
58
50
  .catch(() => false);
@@ -86,7 +78,6 @@ export function createRuntimeCoordinatorService(deps) {
86
78
  void reconcileCurrentProject().catch(() => undefined);
87
79
  },
88
80
  stop() {
89
- deps.roleStallDetector.stop();
90
81
  if (reconcileTimer === undefined) {
91
82
  return;
92
83
  }
@@ -98,7 +89,6 @@ export function createRuntimeCoordinatorService(deps) {
98
89
  async function reconcileCurrentProject() {
99
90
  const project = await deps.projectService.getCurrentProject();
100
91
  if (!project) {
101
- deps.roleStallDetector.stop();
102
92
  return;
103
93
  }
104
94
  await reconcileProject(project.repoRoot);
@@ -721,8 +721,7 @@ export function createSessionService(deps) {
721
721
  return undefined;
722
722
  }
723
723
  const timestamp = now();
724
- const turnWasRunning = current.activityStatus === "running"
725
- && current.expectedRuntimeExitReason !== "role-stall-recovery";
724
+ const turnWasRunning = current.activityStatus === "running";
726
725
  const updated = {
727
726
  ...current,
728
727
  status: input.status,
@@ -738,71 +737,6 @@ export function createSessionService(deps) {
738
737
  await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
739
738
  return { record: updated, turnWasRunning };
740
739
  }
741
- async function persistTaskRoleRecord(repoRoot, taskSlug, record) {
742
- deps.registry.upsert(record);
743
- const config = await deps.projectService.loadConfig(repoRoot);
744
- const task = await deps.taskService.loadTask(repoRoot, taskSlug);
745
- await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, record);
746
- return record;
747
- }
748
- async function recoverRoleSession(repoRoot, taskSlug, input) {
749
- const current = await getTaskRoleSessionView(repoRoot, taskSlug, input.role);
750
- if (!current
751
- || current.id !== input.expectedSessionId
752
- || current.runtimeSessionToken !== input.expectedRuntimeSessionToken
753
- || current.status !== "running"
754
- || current.activityStatus !== "running") {
755
- throw new VcmError({
756
- code: "ROLE_STALL_SESSION_CHANGED",
757
- message: `${input.role} session changed before recovery could begin.`,
758
- statusCode: 409
759
- });
760
- }
761
- if (!current.claudeSessionId) {
762
- throw new VcmError({
763
- code: "CLAUDE_SESSION_MISSING",
764
- message: `${input.role} does not have a confirmed Claude session id to resume.`,
765
- statusCode: 409
766
- });
767
- }
768
- const prepared = await persistTaskRoleRecord(repoRoot, taskSlug, {
769
- ...current,
770
- expectedRuntimeExitReason: "role-stall-recovery",
771
- updatedAt: now()
772
- });
773
- if (deps.runtime.getSession(prepared.id)) {
774
- await deps.runtime.stop(prepared.id);
775
- }
776
- await persistTaskRoleRecord(repoRoot, taskSlug, {
777
- ...prepared,
778
- status: "exited",
779
- activityStatus: "idle",
780
- pid: undefined,
781
- expectedRuntimeExitReason: undefined,
782
- updatedAt: now()
783
- });
784
- const resumed = await launchRoleSession(repoRoot, taskSlug, input.role, {
785
- permissionMode: prepared.permissionMode,
786
- model: prepared.model,
787
- effort: prepared.effort
788
- }, "resume");
789
- if ((await waitForSessionInputReady(resumed.id)) === "exited") {
790
- throw new VcmError({
791
- code: "ROLE_STALL_RESUME_FAILED",
792
- message: `${input.role} session exited before recovery input could be submitted.`,
793
- statusCode: 409
794
- });
795
- }
796
- await submitTerminalInput(deps.runtime, resumed.id, input.recoveryPrompt);
797
- return persistTaskRoleRecord(repoRoot, taskSlug, {
798
- ...resumed,
799
- activityStatus: "running",
800
- lastTurnStartedAt: now(),
801
- lastHookEventAt: now(),
802
- expectedRuntimeExitReason: undefined,
803
- updatedAt: now()
804
- });
805
- }
806
740
  return {
807
741
  async assertModelLaunchReady(model = "default") {
808
742
  await getModelLaunchEnvironment(normalizeClaudeModel(model));
@@ -1307,8 +1241,7 @@ export function createSessionService(deps) {
1307
1241
  : undefined;
1308
1242
  }
1309
1243
  return markTaskRoleActivityIdle(repoRoot, taskSlug, role);
1310
- },
1311
- recoverRoleSession
1244
+ }
1312
1245
  };
1313
1246
  }
1314
1247
  function toRoleSessionRecordView(record, runtime) {
@@ -1398,7 +1331,7 @@ function isSessionMissingError(error) {
1398
1331
  error.code === "SESSION_MISSING";
1399
1332
  }
1400
1333
  function withoutRuntimeOnlySessionFields(session) {
1401
- const { pid: _pid, runtimeSessionToken: _runtimeSessionToken, expectedRuntimeExitReason: _expectedRuntimeExitReason, ...persisted } = session;
1334
+ const { pid: _pid, runtimeSessionToken: _runtimeSessionToken, ...persisted } = session;
1402
1335
  return persisted;
1403
1336
  }
1404
1337
  function defaultIsProcessAlive(pid) {