@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.
- package/README.md +26 -4
- package/README.zh-CN.md +16 -4
- package/package.json +12 -4
- package/src/commands/docs.js +19 -13
- package/src/commands/init.js +64 -11
- package/src/commands/report.js +4 -4
- package/src/commands/status.js +102 -61
- package/src/commands/sync.js +88 -0
- package/src/index.js +9 -3
- package/src/lib/hook-core.js +26 -3
- package/src/lib/host-layout.js +1384 -0
- package/src/lib/output-policy.js +6 -6
- package/src/lib/task-core.js +83 -19
- package/src/runtime-host/index.js +57 -0
package/src/lib/output-policy.js
CHANGED
|
@@ -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", "
|
|
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, ["
|
|
205
|
+
return hasKeyword(taskContext.goal, ["复用决策", "通用方案", "shared decision", "reusable decision", "通用能力"]);
|
|
206
206
|
}
|
|
207
207
|
if (normalized === "architectural_decision") {
|
|
208
|
-
return hasKeyword(taskContext.goal, ["
|
|
208
|
+
return hasKeyword(taskContext.goal, ["架构决策", "architecture decision", "架构边界", "runtime boundary"]);
|
|
209
209
|
}
|
|
210
210
|
if (normalized === "policy_change") {
|
|
211
|
-
return hasKeyword(taskContext.goal, ["
|
|
211
|
+
return hasKeyword(taskContext.goal, ["治理策略", "policy as code", "policy change", "策略变更"]);
|
|
212
212
|
}
|
|
213
213
|
if (normalized === "protocol_change") {
|
|
214
|
-
return hasKeyword(taskContext.goal, ["
|
|
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
|
|
217
|
+
return hasKeyword(taskContext.goal, ["adapter contract", "host contract", "适配器契约", "宿主契约"]);
|
|
218
218
|
}
|
|
219
219
|
return false;
|
|
220
220
|
}
|
package/src/lib/task-core.js
CHANGED
|
@@ -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
|
|
104
|
-
const
|
|
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
|
-
? `
|
|
328
|
+
? ` 阻断:${taskState.open_questions[0]}`
|
|
294
329
|
: "";
|
|
295
330
|
|
|
296
|
-
return
|
|
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
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
: "
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
+
}
|