@brawnen/agent-harness-cli 0.1.1 → 0.1.2

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.
@@ -199,22 +199,22 @@ function matchesArtifactCondition(condition, taskContext) {
199
199
  return inferCrossModuleChange(taskContext.scope);
200
200
  }
201
201
  if (normalized === "public_contract_changed") {
202
- return hasKeyword(taskContext.goal, ["api", "schema", "接口", "契约", "协议", "contract"]);
202
+ return hasKeyword(taskContext.goal, ["public contract", "api schema", "schema change", "breaking change", "接口契约", "公开契约", "兼容性变更"]);
203
203
  }
204
204
  if (normalized === "reusable_decision") {
205
- return hasKeyword(taskContext.goal, ["复用", "通用", "shared", "reusable"]);
205
+ return hasKeyword(taskContext.goal, ["复用决策", "通用方案", "shared decision", "reusable decision", "通用能力"]);
206
206
  }
207
207
  if (normalized === "architectural_decision") {
208
- return hasKeyword(taskContext.goal, ["架构", "architecture", "协议", "adapter", "hook"]);
208
+ return hasKeyword(taskContext.goal, ["架构决策", "architecture decision", "架构边界", "runtime boundary"]);
209
209
  }
210
210
  if (normalized === "policy_change") {
211
- return hasKeyword(taskContext.goal, ["策略", "policy", "规则"]);
211
+ return hasKeyword(taskContext.goal, ["治理策略", "policy as code", "policy change", "策略变更"]);
212
212
  }
213
213
  if (normalized === "protocol_change") {
214
- return hasKeyword(taskContext.goal, ["协议", "protocol"]);
214
+ return hasKeyword(taskContext.goal, ["协议变更", "protocol change", "schema migration"]);
215
215
  }
216
216
  if (normalized === "host_adapter_contract_change") {
217
- return hasKeyword(taskContext.goal, ["adapter", "适配", "hook", "codex", "claude", "gemini"]);
217
+ return hasKeyword(taskContext.goal, ["adapter contract", "host contract", "适配器契约", "宿主契约"]);
218
218
  }
219
219
  return false;
220
220
  }
@@ -31,6 +31,39 @@ const AFFIRMATIVE_SHORT_REPLIES = [
31
31
  "先这样", "就这样", "开始吧", "搞起", "go"
32
32
  ];
33
33
 
34
+ const TASK_REFERENCE_KEYWORDS = [
35
+ "刚才那个",
36
+ "刚才这个",
37
+ "刚才的任务",
38
+ "这个任务",
39
+ "这个问题",
40
+ "这个方案",
41
+ "当前任务",
42
+ "前面那个"
43
+ ];
44
+
45
+ const TASK_REPLY_PREFIXES = [
46
+ "先做",
47
+ "先看",
48
+ "先把",
49
+ "先",
50
+ "再",
51
+ "然后",
52
+ "接着",
53
+ "列一下",
54
+ "看一下",
55
+ "看看",
56
+ "全部",
57
+ "只",
58
+ "统一",
59
+ "收敛成",
60
+ "按这个",
61
+ "就按这个",
62
+ "好,",
63
+ "好,",
64
+ "那就"
65
+ ];
66
+
34
67
  const NEW_TASK_KEYWORDS = [
35
68
  "新任务",
36
69
  "另一个问题",
@@ -59,7 +92,6 @@ const MANUAL_CONFIRMATION_KEYWORDS = [
59
92
  "允许继续",
60
93
  "就按这个做",
61
94
  "按这个做",
62
- "继续",
63
95
  "go ahead",
64
96
  "proceed"
65
97
  ];
@@ -100,8 +132,13 @@ export function buildTaskDraftFromInput(sourceInput, options = {}) {
100
132
  const riskLevel = inferRiskLevel(input, riskSignals);
101
133
  const intent = options.intent ?? inferIntent(input);
102
134
  const mode = options.mode ?? (intent === "explore" ? "explore" : "delivery");
103
- const nextAction = openQuestions.length > 0 ? "clarify" : "plan";
104
- const derivedState = openQuestions.length > 0 ? "needs_clarification" : "planned";
135
+ const hasOpenQuestions = openQuestions.length > 0;
136
+ const nextAction = hasOpenQuestions
137
+ ? (riskLevel === "high" ? "clarify" : "observe")
138
+ : "plan";
139
+ const derivedState = hasOpenQuestions
140
+ ? (riskLevel === "high" ? "needs_clarification" : "draft")
141
+ : "planned";
105
142
 
106
143
  return {
107
144
  schema_version: "0.3",
@@ -247,7 +284,7 @@ export function autoIntakePrompt(cwd, prompt) {
247
284
  }
248
285
 
249
286
  const decision = classifyPromptAgainstTask(input, activeTask);
250
- if (decision.type === "continue") {
287
+ if (decision.type === "continue" || decision.type === "provisional_continue") {
251
288
  return {
252
289
  action: "continue",
253
290
  task: activeTask,
@@ -287,13 +324,11 @@ export function buildCurrentTaskContext(taskState) {
287
324
  }
288
325
 
289
326
  const goal = taskState?.confirmed_contract?.goal ?? taskState?.task_draft?.goal ?? "未定义目标";
290
- const nextAction = deriveNextAction(taskState);
291
- const currentState = taskState?.current_state ?? "unknown";
292
327
  const blockingQuestion = Array.isArray(taskState?.open_questions) && taskState.open_questions.length > 0
293
- ? ` 当前阻断问题:${taskState.open_questions[0]}`
328
+ ? ` 阻断:${taskState.open_questions[0]}`
294
329
  : "";
295
330
 
296
- return `当前 active task: ${taskState.task_id}。目标:${goal}。当前状态:${currentState}。下一步动作:${nextAction}。${blockingQuestion}`.trim();
331
+ return `当前任务 ${taskState.task_id}:${goal}。${blockingQuestion}`.trim();
297
332
  }
298
333
 
299
334
  export function buildNewTaskContext(taskState) {
@@ -302,7 +337,7 @@ export function buildNewTaskContext(taskState) {
302
337
  }
303
338
 
304
339
  const draft = taskState.task_draft ?? {};
305
- return `已自动创建新任务 ${taskState.task_id}。intent=${draft.intent},goal=${draft.goal},next_action=${draft.next_action}。请先按该任务继续。`;
340
+ return `已切换到新任务 ${taskState.task_id}:${draft.goal}。`;
306
341
  }
307
342
 
308
343
  export function classifyUserOverridePrompt(prompt) {
@@ -404,20 +439,44 @@ function classifyPromptAgainstTask(prompt, activeTask) {
404
439
  });
405
440
  }
406
441
 
442
+ const matchedTaskReference = TASK_REFERENCE_KEYWORDS.find((keyword) => normalizedPrompt.includes(keyword));
443
+ if (matchedTaskReference) {
444
+ return buildDecision("continue", {
445
+ reasonCode: "matched_task_reference",
446
+ reason: `输入命中当前任务指代:${matchedTaskReference}。`,
447
+ matchedSignals: [`task_reference:${matchedTaskReference}`],
448
+ confidence: "medium"
449
+ });
450
+ }
451
+
452
+ if (isLikelyTaskReply(normalizedPrompt)) {
453
+ return buildDecision("provisional_continue", {
454
+ reasonCode: "likely_task_reply",
455
+ reason: "输入更像是当前任务内的步骤选择或简短回复,先续接当前任务观察。",
456
+ matchedSignals: ["likely_task_reply"],
457
+ confidence: "low"
458
+ });
459
+ }
460
+
407
461
  const ambiguous = isAmbiguousPrompt(prompt);
408
462
  if (ambiguous) {
409
463
  const matchedHighRiskKeyword = findMatchedKeyword(normalizedPrompt, HIGH_RISK_KEYWORDS);
410
464
  const highRisk = Boolean(matchedHighRiskKeyword);
411
- return buildDecision("clarify", {
412
- block: highRisk,
413
- reasonCode: highRisk ? "ambiguous_high_risk_prompt" : "ambiguous_prompt",
414
- reason: highRisk
415
- ? "输入任务归属不明且含高风险信号,请先澄清。"
416
- : "输入归属不明,请先澄清是延续当前任务还是新任务。",
417
- matchedSignals: highRisk
418
- ? [`high_risk_keyword:${matchedHighRiskKeyword}`, "ambiguous_prompt"]
419
- : ["ambiguous_prompt"],
420
- confidence: highRisk ? "high" : "low"
465
+ if (highRisk) {
466
+ return buildDecision("clarify", {
467
+ block: true,
468
+ reasonCode: "ambiguous_high_risk_prompt",
469
+ reason: "输入任务归属不明且含高风险信号,请先澄清。",
470
+ matchedSignals: [`high_risk_keyword:${matchedHighRiskKeyword}`, "ambiguous_prompt"],
471
+ confidence: "high"
472
+ });
473
+ }
474
+
475
+ return buildDecision("provisional_continue", {
476
+ reasonCode: "ambiguous_low_risk_continue",
477
+ reason: "输入较短且无高风险信号,先按当前任务续接并观察。",
478
+ matchedSignals: ["ambiguous_prompt", "low_risk"],
479
+ confidence: "low"
421
480
  });
422
481
  }
423
482
 
@@ -448,6 +507,11 @@ function isAffirmativeShortReply(normalizedPrompt) {
448
507
  return AFFIRMATIVE_SHORT_REPLIES.some((reply) => trimmed === reply);
449
508
  }
450
509
 
510
+ function isLikelyTaskReply(normalizedPrompt) {
511
+ const trimmed = normalizedPrompt.trim();
512
+ return TASK_REPLY_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
513
+ }
514
+
451
515
  function inferIntent(input) {
452
516
  const normalized = input.toLowerCase();
453
517
  if (EXPLORE_KEYWORDS.some((keyword) => normalized.includes(keyword))) {
@@ -0,0 +1,57 @@
1
+ import { getActiveTask, resolveActiveTaskId, updateTaskState } from "../lib/state-store.js";
2
+
3
+ export {
4
+ blockDecision,
5
+ buildManualFallbackContext,
6
+ continueDecision,
7
+ handleAfterTool,
8
+ handleBeforeTool,
9
+ handleCompletionGate,
10
+ handlePromptSubmit,
11
+ handleSessionStart,
12
+ normalizeHarnessToolName
13
+ } from "../lib/hook-core.js";
14
+ export { buildClaudeHookOutput, resolveClaudeCompletionMessage } from "../lib/hook-io/claude.js";
15
+ export { buildCodexHookOutput, resolveCodexCompletionMessage } from "../lib/hook-io/codex.js";
16
+ export {
17
+ buildGeminiHookOutput,
18
+ resolveGeminiCompletionMessage,
19
+ resolveGeminiToolCommand,
20
+ resolveGeminiToolExitCode,
21
+ resolveGeminiToolName,
22
+ resolveGeminiToolOutput,
23
+ resolveGeminiToolPath
24
+ } from "../lib/hook-io/gemini.js";
25
+
26
+ export function appendMinimalToolEvidence({
27
+ content = null,
28
+ cwd,
29
+ exitCode = 0,
30
+ toolName = null,
31
+ type = "command_result"
32
+ }) {
33
+ const taskId = resolveActiveTaskId(cwd);
34
+ const activeTask = getActiveTask(cwd);
35
+
36
+ if (!taskId || !activeTask || ["done", "failed", "suspended"].includes(activeTask.current_state)) {
37
+ return false;
38
+ }
39
+
40
+ const safeToolName = typeof toolName === "string" && toolName.trim().length > 0
41
+ ? toolName.trim()
42
+ : "<unknown tool>";
43
+ const evidenceContent = typeof content === "string" && content.trim().length > 0
44
+ ? content.trim()
45
+ : `Tool: ${safeToolName}`;
46
+
47
+ updateTaskState(cwd, taskId, {
48
+ evidence: [{
49
+ content: evidenceContent,
50
+ exit_code: typeof exitCode === "number" ? exitCode : 0,
51
+ timestamp: new Date().toISOString(),
52
+ type
53
+ }]
54
+ });
55
+
56
+ return true;
57
+ }