@pencil-agent/nano-pencil 1.13.8 → 1.13.9
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/build-meta.json +3 -3
- package/dist/builtin-extensions.js +10 -0
- package/dist/core/config/settings-manager.d.ts +7 -0
- package/dist/core/sub-agent/index.d.ts +1 -1
- package/dist/core/sub-agent/sub-agent-backend.js +78 -12
- package/dist/core/sub-agent/sub-agent-types.d.ts +44 -1
- package/dist/core/sub-agent/sub-agent-types.js +1 -1
- package/dist/extensions/defaults/CLAUDE.md +5 -4
- package/dist/extensions/defaults/grub/grub-controller.d.ts +2 -0
- package/dist/extensions/defaults/grub/grub-controller.js +79 -24
- package/dist/extensions/defaults/grub/grub-i18n.d.ts +128 -0
- package/dist/extensions/defaults/grub/grub-i18n.js +167 -0
- package/dist/extensions/defaults/grub/grub-parser.d.ts +2 -1
- package/dist/extensions/defaults/grub/grub-parser.js +5 -3
- package/dist/extensions/defaults/grub/grub-types.d.ts +3 -0
- package/dist/extensions/defaults/grub/index.js +133 -78
- package/dist/extensions/defaults/idle-think/curiosity.d.ts +46 -0
- package/dist/extensions/defaults/idle-think/curiosity.js +137 -0
- package/dist/extensions/defaults/idle-think/index.d.ts +15 -0
- package/dist/extensions/defaults/idle-think/index.js +169 -0
- package/dist/extensions/defaults/idle-think/insights.d.ts +40 -0
- package/dist/extensions/defaults/idle-think/insights.js +123 -0
- package/dist/extensions/defaults/idle-think/thinker.d.ts +26 -0
- package/dist/extensions/defaults/idle-think/thinker.js +208 -0
- package/dist/extensions/defaults/presence/index.d.ts +1 -0
- package/dist/extensions/defaults/presence/index.js +83 -10
- package/dist/extensions/defaults/team/CLAUDE.md +7 -6
- package/dist/extensions/defaults/team/index.d.ts +1 -0
- package/dist/extensions/defaults/team/index.js +92 -3
- package/dist/extensions/defaults/team/team-dashboard.js +6 -0
- package/dist/extensions/defaults/team/team-parser.d.ts +1 -1
- package/dist/extensions/defaults/team/team-parser.js +3 -2
- package/dist/extensions/defaults/team/team-presets.d.ts +14 -2
- package/dist/extensions/defaults/team/team-presets.js +124 -4
- package/dist/extensions/defaults/team/team-runtime.d.ts +15 -2
- package/dist/extensions/defaults/team/team-runtime.js +62 -1
- package/dist/extensions/defaults/team/team-types.d.ts +9 -0
- package/dist/modes/interactive/components/pencil-loader.d.ts +0 -2
- package/dist/modes/interactive/components/pencil-loader.js +1 -14
- package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +0 -17
- package/dist/node_modules/@pencil-agent/ai/models.generated.js +1 -18
- package/package.json +1 -1
package/dist/build-meta.json
CHANGED
|
@@ -24,6 +24,7 @@ const BUNDLED_SAL_EXTENSION = join(__dirname, "extensions", "defaults", "sal", "
|
|
|
24
24
|
const BUNDLED_GRUB_EXTENSION = join(__dirname, "extensions", "defaults", "grub", "index.js");
|
|
25
25
|
const BUNDLED_SUBAGENT_EXTENSION = join(__dirname, "extensions", "defaults", "subagent", "index.js");
|
|
26
26
|
const BUNDLED_TEAM_EXTENSION = join(__dirname, "extensions", "defaults", "team", "index.js");
|
|
27
|
+
const BUNDLED_IDLE_THINK_EXTENSION = join(__dirname, "extensions", "defaults", "idle-think", "index.js");
|
|
27
28
|
const BUNDLED_BTW_EXTENSION = join(__dirname, "extensions", "defaults", "btw", "index.js");
|
|
28
29
|
const BUNDLED_DEBUG_EXTENSION = join(__dirname, "extensions", "defaults", "debug", "index.js");
|
|
29
30
|
const BUNDLED_MCP_EXTENSION = join(__dirname, "extensions", "defaults", "mcp", "index.js");
|
|
@@ -211,6 +212,15 @@ export function getBuiltinExtensionPaths() {
|
|
|
211
212
|
if (existsSync(teamTs))
|
|
212
213
|
paths.push(teamTs);
|
|
213
214
|
}
|
|
215
|
+
// === IdleThink extension (background code exploration during idle) ===
|
|
216
|
+
if (existsSync(BUNDLED_IDLE_THINK_EXTENSION)) {
|
|
217
|
+
paths.push(BUNDLED_IDLE_THINK_EXTENSION);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const idleThinkTs = join(__dirname, "extensions", "defaults", "idle-think", "index.ts");
|
|
221
|
+
if (existsSync(idleThinkTs))
|
|
222
|
+
paths.push(idleThinkTs);
|
|
223
|
+
}
|
|
214
224
|
// === BTW extension (quick side question without interrupting) ===
|
|
215
225
|
if (existsSync(BUNDLED_BTW_EXTENSION)) {
|
|
216
226
|
paths.push(BUNDLED_BTW_EXTENSION);
|
|
@@ -105,6 +105,13 @@ export interface Settings {
|
|
|
105
105
|
presence?: {
|
|
106
106
|
enabled?: boolean;
|
|
107
107
|
};
|
|
108
|
+
/** IdleThink extension settings - background code exploration during idle */
|
|
109
|
+
idleThink?: {
|
|
110
|
+
enabled?: boolean;
|
|
111
|
+
idleMinutes?: number;
|
|
112
|
+
dailyBudget?: number;
|
|
113
|
+
maxDurationMinutes?: number;
|
|
114
|
+
};
|
|
108
115
|
/** Auto-update setting: 'always' = auto-update on startup, 'prompt' = ask user (default), 'never' = never check */
|
|
109
116
|
autoUpdate?: "always" | "prompt" | "never";
|
|
110
117
|
/** Last skipped version for update prompts */
|
|
@@ -8,4 +8,4 @@ export { SubAgentRuntime, subAgentRuntime } from "./sub-agent-runtime.js";
|
|
|
8
8
|
export { InProcessSubAgentBackend } from "./sub-agent-backend.js";
|
|
9
9
|
export { SubprocessSubAgentBackend } from "./subprocess-backend.js";
|
|
10
10
|
export type { SubprocessBackendOptions } from "./subprocess-backend.js";
|
|
11
|
-
export type { SubAgentSpec, SubAgentResult, SubAgentHandle, SubAgentBackend, } from "./sub-agent-types.js";
|
|
11
|
+
export type { SubAgentSpec, SubAgentEvent, SubAgentResult, SubAgentHandle, SubAgentBackend, } from "./sub-agent-types.js";
|
|
@@ -32,6 +32,12 @@ export class InProcessSubAgentBackend {
|
|
|
32
32
|
model: spec.model,
|
|
33
33
|
};
|
|
34
34
|
const { session } = await createAgentSession(options);
|
|
35
|
+
const unsubscribe = session.subscribe((event) => {
|
|
36
|
+
const subAgentEvent = toSubAgentEvent(id, event);
|
|
37
|
+
if (subAgentEvent) {
|
|
38
|
+
spec.onEvent?.(subAgentEvent);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
35
41
|
const timeoutMs = spec.timeoutMs;
|
|
36
42
|
let status = "running";
|
|
37
43
|
let result;
|
|
@@ -44,21 +50,10 @@ export class InProcessSubAgentBackend {
|
|
|
44
50
|
}
|
|
45
51
|
}, timeoutMs);
|
|
46
52
|
}
|
|
47
|
-
// Extract text from assistant message content
|
|
48
|
-
const extractTextFromContent = (content) => {
|
|
49
|
-
if (typeof content === "string")
|
|
50
|
-
return content;
|
|
51
|
-
if (Array.isArray(content)) {
|
|
52
|
-
return content
|
|
53
|
-
.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && typeof part.text === "string")
|
|
54
|
-
.map((part) => part.text)
|
|
55
|
-
.join("\n");
|
|
56
|
-
}
|
|
57
|
-
return "";
|
|
58
|
-
};
|
|
59
53
|
// Start the prompt
|
|
60
54
|
const promptPromise = (async () => {
|
|
61
55
|
try {
|
|
56
|
+
spec.onEvent?.({ type: "agent_start", subAgentId: id, timestamp: Date.now() });
|
|
62
57
|
await session.prompt(prompt, {
|
|
63
58
|
images: spec.images,
|
|
64
59
|
});
|
|
@@ -107,6 +102,14 @@ export class InProcessSubAgentBackend {
|
|
|
107
102
|
}
|
|
108
103
|
// Clean up signal handler
|
|
109
104
|
spec.signal.removeEventListener("abort", signalHandler);
|
|
105
|
+
unsubscribe();
|
|
106
|
+
spec.onEvent?.({
|
|
107
|
+
type: "agent_end",
|
|
108
|
+
subAgentId: id,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
success: result?.success ?? false,
|
|
111
|
+
error: result?.error,
|
|
112
|
+
});
|
|
110
113
|
}
|
|
111
114
|
})();
|
|
112
115
|
return {
|
|
@@ -132,6 +135,69 @@ export class InProcessSubAgentBackend {
|
|
|
132
135
|
};
|
|
133
136
|
}
|
|
134
137
|
}
|
|
138
|
+
function toSubAgentEvent(subAgentId, event) {
|
|
139
|
+
const timestamp = Date.now();
|
|
140
|
+
switch (event.type) {
|
|
141
|
+
case "message_update":
|
|
142
|
+
return {
|
|
143
|
+
type: "message_update",
|
|
144
|
+
subAgentId,
|
|
145
|
+
timestamp,
|
|
146
|
+
text: extractMessageText(event.message),
|
|
147
|
+
deltaType: event.assistantMessageEvent.type,
|
|
148
|
+
};
|
|
149
|
+
case "message_end":
|
|
150
|
+
return {
|
|
151
|
+
type: "message_end",
|
|
152
|
+
subAgentId,
|
|
153
|
+
timestamp,
|
|
154
|
+
text: extractMessageText(event.message),
|
|
155
|
+
};
|
|
156
|
+
case "tool_execution_start":
|
|
157
|
+
return {
|
|
158
|
+
type: "tool_start",
|
|
159
|
+
subAgentId,
|
|
160
|
+
timestamp,
|
|
161
|
+
toolName: event.toolName,
|
|
162
|
+
args: event.args,
|
|
163
|
+
};
|
|
164
|
+
case "tool_execution_update":
|
|
165
|
+
return {
|
|
166
|
+
type: "tool_update",
|
|
167
|
+
subAgentId,
|
|
168
|
+
timestamp,
|
|
169
|
+
toolName: event.toolName,
|
|
170
|
+
partialResult: event.partialResult,
|
|
171
|
+
};
|
|
172
|
+
case "tool_execution_end":
|
|
173
|
+
return {
|
|
174
|
+
type: "tool_end",
|
|
175
|
+
subAgentId,
|
|
176
|
+
timestamp,
|
|
177
|
+
toolName: event.toolName,
|
|
178
|
+
isError: event.isError,
|
|
179
|
+
};
|
|
180
|
+
default:
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function extractTextFromContent(content) {
|
|
185
|
+
if (typeof content === "string")
|
|
186
|
+
return content;
|
|
187
|
+
if (Array.isArray(content)) {
|
|
188
|
+
return content
|
|
189
|
+
.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && typeof part.text === "string")
|
|
190
|
+
.map((part) => part.text)
|
|
191
|
+
.join("\n");
|
|
192
|
+
}
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
function extractMessageText(message) {
|
|
196
|
+
if (typeof message !== "object" || message === null || !("content" in message)) {
|
|
197
|
+
return "";
|
|
198
|
+
}
|
|
199
|
+
return extractTextFromContent(message.content);
|
|
200
|
+
}
|
|
135
201
|
async function buildPromptWithContextFiles(spec) {
|
|
136
202
|
if (!spec.contextFiles?.length) {
|
|
137
203
|
return spec.prompt;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: SubAgent types - SubAgentSpec, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
2
|
+
* [WHO]: SubAgent types - SubAgentSpec, SubAgentEvent, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
3
3
|
* [FROM]: Depends on @pencil-agent/agent-core, @pencil-agent/ai, core/tools
|
|
4
4
|
* [TO]: Consumed by ./sub-agent-runtime, ./sub-agent-backend, ./index.ts, extensions/defaults/subagent/*, extensions/defaults/team/*
|
|
5
5
|
* [HERE]: core/sub-agent/sub-agent-types.ts - SubAgent type definitions
|
|
@@ -7,6 +7,47 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { ImageContent, Model } from "@pencil-agent/ai";
|
|
9
9
|
import type { Tool } from "../tools/index.js";
|
|
10
|
+
/** Realtime lifecycle event emitted by a running SubAgent. */
|
|
11
|
+
export type SubAgentEvent = {
|
|
12
|
+
type: "agent_start";
|
|
13
|
+
subAgentId: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: "message_update";
|
|
17
|
+
subAgentId: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
text: string;
|
|
20
|
+
deltaType?: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "message_end";
|
|
23
|
+
subAgentId: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
text: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "tool_start";
|
|
28
|
+
subAgentId: string;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
toolName: string;
|
|
31
|
+
args: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
type: "tool_update";
|
|
34
|
+
subAgentId: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
toolName: string;
|
|
37
|
+
partialResult: unknown;
|
|
38
|
+
} | {
|
|
39
|
+
type: "tool_end";
|
|
40
|
+
subAgentId: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
toolName: string;
|
|
43
|
+
isError: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
type: "agent_end";
|
|
46
|
+
subAgentId: string;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
success: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
10
51
|
/**
|
|
11
52
|
* Specification for spawning a SubAgent.
|
|
12
53
|
*/
|
|
@@ -29,6 +70,8 @@ export interface SubAgentSpec {
|
|
|
29
70
|
contextFiles?: string[];
|
|
30
71
|
/** Optional callback invoked after the run result is available */
|
|
31
72
|
exitHook?: (result: SubAgentResult) => Promise<void> | void;
|
|
73
|
+
/** Optional realtime observer for TUI/status integrations */
|
|
74
|
+
onEvent?: (event: SubAgentEvent) => void;
|
|
32
75
|
}
|
|
33
76
|
/**
|
|
34
77
|
* Result from a completed SubAgent run.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: SubAgent types - SubAgentSpec, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
2
|
+
* [WHO]: SubAgent types - SubAgentSpec, SubAgentEvent, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
3
3
|
* [FROM]: Depends on @pencil-agent/agent-core, @pencil-agent/ai, core/tools
|
|
4
4
|
* [TO]: Consumed by ./sub-agent-runtime, ./sub-agent-backend, ./index.ts, extensions/defaults/subagent/*, extensions/defaults/team/*
|
|
5
5
|
* [HERE]: core/sub-agent/sub-agent-types.ts - SubAgent type definitions
|
|
@@ -17,10 +17,11 @@ security-audit/engine/interceptor.ts: Request/response interception, Interceptor
|
|
|
17
17
|
security-audit/engine/logger.ts: Security event logging, JSON file audit trail
|
|
18
18
|
security-audit/engine/detector.ts: Vulnerability detection, pattern matching for dangerous commands
|
|
19
19
|
soul/index.ts: AI personality evolution extension, persistent personality across sessions
|
|
20
|
-
grub/index.ts: Grub extension entry - long-running autonomous harness, dual-phase system prompts (initializer/coding), /grub command (start/status/resume/stop) + grub renderer, session_start auto-adopt, git harness commit, pruneStale cleanup
|
|
21
|
-
grub/grub-controller.ts: GrubController - state machine for /grub iterations, durable persistState on every transition, adoptResumedTask for cross-session resume, validateCompletion downgrades premature complete when feature-list still has pending entries
|
|
22
|
-
grub/grub-parser.ts: Grub command parsing - parseGrubCommand/buildGrubHelp with resume subcommand, status --json, --max-iter/--max-fail flags
|
|
23
|
-
grub/grub-types.ts: Grub types - GrubStatus/GrubDecisionStatus/GrubDecision/GrubPhase/GrubTaskState/GrubTaskSnapshot/ParsedGrubCommand + FeatureItem/FeatureList (version 1 schema) + PersistedGrubState envelope
|
|
20
|
+
grub/index.ts: Grub extension entry - long-running autonomous harness, locale-aware dual-phase system prompts (initializer/coding), /grub command (start/status/resume/stop) + grub renderer, session_start auto-adopt, git harness commit, pruneStale cleanup
|
|
21
|
+
grub/grub-controller.ts: GrubController - state machine for /grub iterations, locale-persisted prompt generation, durable persistState on every transition, adoptResumedTask for cross-session resume, validateCompletion downgrades premature complete when feature-list still has pending entries
|
|
22
|
+
grub/grub-parser.ts: Grub command parsing - parseGrubCommand/buildGrubHelp with localized help, resume subcommand, status --json, --max-iter/--max-fail flags
|
|
23
|
+
grub/grub-types.ts: Grub types - GrubStatus/GrubDecisionStatus/GrubDecision/GrubPhase/GrubLocale/GrubTaskState/GrubTaskSnapshot/ParsedGrubCommand + FeatureItem/FeatureList (version 1 schema) + PersistedGrubState envelope
|
|
24
|
+
grub/grub-i18n.ts: Grub localization helper - detectGrubLocale(), grubText(), languageName(), English/Chinese TUI strings
|
|
24
25
|
grub/grub-feature-list.ts: feature-list.json IO - readFeatureList/writeFeatureList atomic write, validateFeatureListDiff enforces passes/evidence-only mutations, createInitialFeatureList placeholder, migrateChecklistToFeatureList legacy converter, countPassing/allPassing/firstPending helpers
|
|
25
26
|
grub/grub-persistence.ts: Cross-session persistence - persistState atomic JSON write to .grub/<id>/state.json, loadState shape-validated read, discoverActiveTasks scans .grub/ for running records, pruneStale removes terminal harnesses older than 30 days by default
|
|
26
27
|
grub/README.md: Grub extension documentation - long-running harness contract, feature-list.json schema, completion guard, cross-session resume, legacy migration
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* [TO]: Consumed by extension entry point (./index.ts)
|
|
5
5
|
* [HERE]: extensions/defaults/grub/grub-controller.ts - state machine for /grub iterations with cross-session persistence and feature-list-gated completion
|
|
6
6
|
*/
|
|
7
|
+
import { type GrubLocale } from "./grub-i18n.js";
|
|
7
8
|
import type { GrubControllerState, GrubDecision, GrubTaskSnapshot, GrubTaskState } from "./grub-types.js";
|
|
8
9
|
export interface GrubStartOptions {
|
|
9
10
|
maxIterations?: number;
|
|
10
11
|
maxConsecutiveFailures?: number;
|
|
12
|
+
locale?: GrubLocale;
|
|
11
13
|
}
|
|
12
14
|
export declare class GrubController {
|
|
13
15
|
private activeTask?;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { allPassing, firstPending, readFeatureList } from "./grub-feature-list.js";
|
|
10
|
+
import { languageName, grubText } from "./grub-i18n.js";
|
|
10
11
|
import { persistState, stateFilePathFor } from "./grub-persistence.js";
|
|
11
12
|
const DEFAULT_MAX_ITERATIONS = 25;
|
|
12
13
|
const DEFAULT_MAX_CONSECUTIVE_FAILURES = 3;
|
|
@@ -39,6 +40,7 @@ export class GrubController {
|
|
|
39
40
|
const task = {
|
|
40
41
|
id,
|
|
41
42
|
goal: trimmedGoal,
|
|
43
|
+
locale: options.locale ?? "en",
|
|
42
44
|
status: "running",
|
|
43
45
|
phase: "initializer",
|
|
44
46
|
startedAt: now,
|
|
@@ -67,7 +69,7 @@ export class GrubController {
|
|
|
67
69
|
if (this.activeTask && this.activeTask.id !== task.id) {
|
|
68
70
|
throw new Error(`Cannot adopt task ${task.id}; ${this.activeTask.id} is already active.`);
|
|
69
71
|
}
|
|
70
|
-
const resumed = { ...task, awaitingTurn: false, updatedAt: Date.now() };
|
|
72
|
+
const resumed = { ...task, locale: task.locale ?? "en", awaitingTurn: false, updatedAt: Date.now() };
|
|
71
73
|
this.activeTask = resumed;
|
|
72
74
|
this.safePersist(resumed);
|
|
73
75
|
return resumed;
|
|
@@ -82,6 +84,7 @@ export class GrubController {
|
|
|
82
84
|
const snapshot = {
|
|
83
85
|
id: finalTask.id,
|
|
84
86
|
goal: finalTask.goal,
|
|
87
|
+
locale: finalTask.locale,
|
|
85
88
|
status,
|
|
86
89
|
phase: finalTask.phase,
|
|
87
90
|
startedAt: finalTask.startedAt,
|
|
@@ -109,36 +112,68 @@ export class GrubController {
|
|
|
109
112
|
throw new Error("No active grub task.");
|
|
110
113
|
}
|
|
111
114
|
const task = this.activeTask;
|
|
115
|
+
const text = grubText(task.locale);
|
|
112
116
|
const sections = [
|
|
113
117
|
`${this.getPromptPrefix(task.id)}${task.currentIteration}]`,
|
|
114
118
|
"",
|
|
115
|
-
"Autonomous grub goal:",
|
|
119
|
+
task.locale === "zh" ? "自主 Grub 目标:" : "Autonomous grub goal:",
|
|
116
120
|
task.goal,
|
|
117
121
|
"",
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
task.locale === "zh"
|
|
123
|
+
? "你正在一个受控的 grub harness 中工作。请围绕同一个目标持续推进具体进展。"
|
|
124
|
+
: "You are inside a managed grub harness. Keep making concrete progress on the same goal.",
|
|
125
|
+
task.locale === "zh"
|
|
126
|
+
? "按需使用工具、编辑文件、运行检查并验证结果。所有面向用户的总结、进度和说明都必须使用中文。"
|
|
127
|
+
: "Use tools, edit files, run checks, and verify results as needed.",
|
|
128
|
+
`User language: ${languageName(task.locale)}.`,
|
|
120
129
|
"",
|
|
121
|
-
"Harness files (must stay up to date every iteration):",
|
|
122
|
-
`-
|
|
123
|
-
`-
|
|
124
|
-
`-
|
|
130
|
+
task.locale === "zh" ? "Harness 文件(每轮都必须保持最新):" : "Harness files (must stay up to date every iteration):",
|
|
131
|
+
`- ${text.featureList}: ${task.featureListPath}`,
|
|
132
|
+
`- ${text.progressLog}: ${task.progressLogPath}`,
|
|
133
|
+
`- ${text.initScript}: ${task.initScriptPath}`,
|
|
125
134
|
];
|
|
126
135
|
if (task.phase === "initializer") {
|
|
127
|
-
sections.push("",
|
|
136
|
+
sections.push("", task.locale === "zh" ? "初始化阶段要求:" : "Initializer phase requirements:", task.locale === "zh"
|
|
137
|
+
? "1. 将 feature-list.json 的占位内容替换为 15-40 个具体、可测试的切片。每项必须保持 {id, category, description, steps[], passes:false}。"
|
|
138
|
+
: "1. Replace the placeholder feature-list.json with 15-40 concrete, testable slices. Every entry MUST keep the schema {id, category, description, steps[], passes:false}.", task.locale === "zh"
|
|
139
|
+
? "2. 确保 init.sh 包含可靠的启动检查,并设置为可执行。"
|
|
140
|
+
: "2. Ensure init.sh contains reliable startup checks and make it executable.", task.locale === "zh"
|
|
141
|
+
? "3. 在 progress-log.md 中追加清晰的初始化总结。"
|
|
142
|
+
: "3. Append a clear initialization summary in progress-log.md.", task.locale === "zh"
|
|
143
|
+
? "4. 先建立强 harness,不要开始大范围实现。"
|
|
144
|
+
: "4. Do not attempt broad implementation yet; prepare a strong harness first.", task.locale === "zh"
|
|
145
|
+
? "5. 除非目标已经完成或阻塞,否则本轮以 loop-state status=continue 结束。"
|
|
146
|
+
: "5. End this turn with loop-state status=continue unless the goal is already complete/blocked.");
|
|
128
147
|
}
|
|
129
148
|
else {
|
|
130
|
-
sections.push("",
|
|
149
|
+
sections.push("", task.locale === "zh" ? "执行阶段要求:" : "Execution phase requirements:", task.locale === "zh"
|
|
150
|
+
? "1. 先运行 init.sh,再读取 feature-list.json 和 progress-log.md。"
|
|
151
|
+
: "1. Start by running the init script, then read feature-list.json and progress-log.md.", task.locale === "zh"
|
|
152
|
+
? "2. 只选择一个 passes:false 的 feature,并端到端完成它。"
|
|
153
|
+
: "2. Pick exactly one feature with passes:false and execute it end-to-end.", task.locale === "zh"
|
|
154
|
+
? "3. 运行相关验证(测试、烟测或运行时检查)。"
|
|
155
|
+
: "3. Run relevant verification (tests, smoke checks, or runtime checks).", task.locale === "zh"
|
|
156
|
+
? "4. 只能修改该 feature 的 passes/evidence 字段;其他字段不可变。"
|
|
157
|
+
: "4. Flip ONLY the passes/evidence fields for that feature; other fields are immutable.", task.locale === "zh"
|
|
158
|
+
? "5. 本轮结束前追加进度日志并 git commit。"
|
|
159
|
+
: "5. Append progress log and git-commit before finishing the turn.", task.locale === "zh"
|
|
160
|
+
? "6. 每轮都保持增量、安全、可回退。"
|
|
161
|
+
: "6. Keep each iteration incremental and production-safe.");
|
|
131
162
|
}
|
|
132
163
|
if (task.lastDecision?.summary) {
|
|
133
|
-
sections.push("", "Previous summary:", task.lastDecision.summary);
|
|
164
|
+
sections.push("", task.locale === "zh" ? "上次总结:" : "Previous summary:", task.lastDecision.summary);
|
|
134
165
|
}
|
|
135
166
|
if (task.lastDecision?.nextStep) {
|
|
136
|
-
sections.push("", "Previous planned next step:", task.lastDecision.nextStep);
|
|
167
|
+
sections.push("", task.locale === "zh" ? "上次计划的下一步:" : "Previous planned next step:", task.lastDecision.nextStep);
|
|
137
168
|
}
|
|
138
169
|
if (task.lastError) {
|
|
139
|
-
sections.push("", "Recovery note:", task.lastError);
|
|
170
|
+
sections.push("", task.locale === "zh" ? "恢复提示:" : "Recovery note:", task.lastError);
|
|
140
171
|
}
|
|
141
|
-
sections.push("",
|
|
172
|
+
sections.push("", task.locale === "zh"
|
|
173
|
+
? "不要因为一次查询结束就停止。只有 feature-list.json 中每个 feature 都 passes:true 时,才可以决定 `complete`。"
|
|
174
|
+
: "Do not stop just because one query finished. Only decide `complete` when every feature in feature-list.json has passes:true.", task.locale === "zh"
|
|
175
|
+
? "如果还需要下一轮自主推进,请以有效的 <loop-state> 块结束,让系统自动继续。"
|
|
176
|
+
: "If you need another autonomous pass, end with a valid <loop-state> block so the system can continue automatically.");
|
|
142
177
|
return sections.join("\n");
|
|
143
178
|
}
|
|
144
179
|
markDispatched() {
|
|
@@ -165,12 +200,14 @@ export class GrubController {
|
|
|
165
200
|
const rewritten = {
|
|
166
201
|
status: "continue",
|
|
167
202
|
summary: decision.summary,
|
|
168
|
-
nextStep:
|
|
203
|
+
nextStep: this.activeTask.locale === "zh"
|
|
204
|
+
? "feature-list.json 缺失或无效;初始化阶段必须先生成它,不能直接声明完成。"
|
|
205
|
+
: "feature-list.json is missing or invalid; the initializer must produce it before claiming complete.",
|
|
169
206
|
};
|
|
170
207
|
return {
|
|
171
208
|
decision: rewritten,
|
|
172
209
|
downgraded: true,
|
|
173
|
-
reason: "feature-list.json missing or invalid",
|
|
210
|
+
reason: this.activeTask.locale === "zh" ? "feature-list.json 缺失或无效" : "feature-list.json missing or invalid",
|
|
174
211
|
};
|
|
175
212
|
}
|
|
176
213
|
if (allPassing(list)) {
|
|
@@ -181,13 +218,19 @@ export class GrubController {
|
|
|
181
218
|
status: "continue",
|
|
182
219
|
summary: decision.summary,
|
|
183
220
|
nextStep: pending
|
|
184
|
-
?
|
|
185
|
-
|
|
221
|
+
? this.activeTask.locale === "zh"
|
|
222
|
+
? `完成待处理 feature:${pending.id}(${pending.description})`
|
|
223
|
+
: `Complete pending feature: ${pending.id} (${pending.description})`
|
|
224
|
+
: this.activeTask.locale === "zh"
|
|
225
|
+
? "先完成剩余待处理 feature,再声明完成。"
|
|
226
|
+
: "Complete the remaining pending features before declaring done.",
|
|
186
227
|
};
|
|
187
228
|
return {
|
|
188
229
|
decision: rewritten,
|
|
189
230
|
downgraded: true,
|
|
190
|
-
reason:
|
|
231
|
+
reason: this.activeTask.locale === "zh"
|
|
232
|
+
? `feature-list 仍有 ${list.features.length - list.features.filter((f) => f.passes).length} 个待处理条目`
|
|
233
|
+
: `feature-list still has ${list.features.length - list.features.filter((f) => f.passes).length} pending entries`,
|
|
191
234
|
};
|
|
192
235
|
}
|
|
193
236
|
finishTurn(decision) {
|
|
@@ -204,15 +247,23 @@ export class GrubController {
|
|
|
204
247
|
task.phase = "execution";
|
|
205
248
|
}
|
|
206
249
|
if (decision.status === "complete") {
|
|
207
|
-
return {
|
|
250
|
+
return {
|
|
251
|
+
action: "stop",
|
|
252
|
+
snapshot: this.stop(task.locale === "zh" ? "Grub 目标已完成。" : "Grub goal completed.", "complete"),
|
|
253
|
+
};
|
|
208
254
|
}
|
|
209
255
|
if (decision.status === "blocked") {
|
|
210
|
-
return {
|
|
256
|
+
return {
|
|
257
|
+
action: "stop",
|
|
258
|
+
snapshot: this.stop(task.locale === "zh" ? "Grub 报告任务被阻塞。" : "Grub reported it is blocked.", "blocked"),
|
|
259
|
+
};
|
|
211
260
|
}
|
|
212
261
|
if (task.currentIteration >= task.maxIterations) {
|
|
213
262
|
return {
|
|
214
263
|
action: "stop",
|
|
215
|
-
snapshot: this.stop(
|
|
264
|
+
snapshot: this.stop(task.locale === "zh"
|
|
265
|
+
? `Grub 达到轮次上限(${task.maxIterations})。`
|
|
266
|
+
: `Grub hit the iteration limit (${task.maxIterations}).`, "failed"),
|
|
216
267
|
};
|
|
217
268
|
}
|
|
218
269
|
task.currentIteration += 1;
|
|
@@ -231,13 +282,17 @@ export class GrubController {
|
|
|
231
282
|
if (task.consecutiveFailures >= task.maxConsecutiveFailures) {
|
|
232
283
|
return {
|
|
233
284
|
action: "stop",
|
|
234
|
-
snapshot: this.stop(
|
|
285
|
+
snapshot: this.stop(task.locale === "zh"
|
|
286
|
+
? `Grub 连续失败 ${task.consecutiveFailures} 次后停止。最近错误:${message}`
|
|
287
|
+
: `Grub stopped after ${task.consecutiveFailures} consecutive failures. Last error: ${message}`, "failed"),
|
|
235
288
|
};
|
|
236
289
|
}
|
|
237
290
|
if (task.currentIteration >= task.maxIterations) {
|
|
238
291
|
return {
|
|
239
292
|
action: "stop",
|
|
240
|
-
snapshot: this.stop(
|
|
293
|
+
snapshot: this.stop(task.locale === "zh"
|
|
294
|
+
? `Grub 达到轮次上限(${task.maxIterations})。`
|
|
295
|
+
: `Grub hit the iteration limit (${task.maxIterations}).`, "failed"),
|
|
241
296
|
};
|
|
242
297
|
}
|
|
243
298
|
task.currentIteration += 1;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: Provides detectGrubLocale(), grubText(), type GrubLocale for localized /grub prompts and TUI messages
|
|
3
|
+
* [FROM]: Depends on core/i18n locale type
|
|
4
|
+
* [TO]: Consumed by grub-controller.ts, grub-parser.ts, index.ts for user-language-aware Grub UX
|
|
5
|
+
* [HERE]: extensions/defaults/grub/grub-i18n.ts - small locale helper scoped to the Grub extension
|
|
6
|
+
*/
|
|
7
|
+
import type { Locale } from "../../../core/i18n/index.js";
|
|
8
|
+
export type GrubLocale = Locale;
|
|
9
|
+
export declare function detectGrubLocale(text: string, fallback?: Locale): GrubLocale;
|
|
10
|
+
export declare function languageName(locale: GrubLocale): string;
|
|
11
|
+
export declare function grubText(locale: GrubLocale): (typeof GRUB_TEXT)[GrubLocale];
|
|
12
|
+
declare const GRUB_TEXT: {
|
|
13
|
+
readonly en: {
|
|
14
|
+
readonly prefix: "[Grub]";
|
|
15
|
+
readonly missingGoal: "Missing grub goal.";
|
|
16
|
+
readonly usage: readonly ["[Grub] Usage:", " /grub <goal> [--max-iter N] [--max-fail N] Start an autonomous digging task", " /grub status [--json] Show the active or last finished task", " /grub resume Resume an adopted task from disk", " /grub stop Stop the active task", "", "[Grub] Harness artifacts under .grub/<task-id>/:", " feature-list.json structured features (agent may only flip passes/evidence)", " progress-log.md append-only progress notes", " init.sh per-iteration get-bearings + smoke script", " state.json durable GrubController state (for cross-session resume)", "", "[Grub] The agent keeps iterating until it reports complete, reports blocked,", "or hits a safety limit (iterations / consecutive failures). Declaring complete", "is rejected unless every feature in feature-list.json has passes:true."];
|
|
17
|
+
readonly activeTask: "Active task";
|
|
18
|
+
readonly lastTask: "Last task";
|
|
19
|
+
readonly status: "Status";
|
|
20
|
+
readonly phase: "Phase";
|
|
21
|
+
readonly goal: "Goal";
|
|
22
|
+
readonly started: "Started";
|
|
23
|
+
readonly updated: "Updated";
|
|
24
|
+
readonly currentIteration: "Current iteration";
|
|
25
|
+
readonly completedIterations: "Completed iterations";
|
|
26
|
+
readonly awaitingResult: "Awaiting result";
|
|
27
|
+
readonly yes: "yes";
|
|
28
|
+
readonly no: "no";
|
|
29
|
+
readonly consecutiveFailures: "Consecutive failures";
|
|
30
|
+
readonly maxIterations: "Max iterations";
|
|
31
|
+
readonly harnessDir: "Harness dir";
|
|
32
|
+
readonly featureList: "Feature list";
|
|
33
|
+
readonly progressLog: "Progress log";
|
|
34
|
+
readonly initScript: "Init script";
|
|
35
|
+
readonly stateFile: "State file";
|
|
36
|
+
readonly featuresPassing: (passing: number, total: number) => string;
|
|
37
|
+
readonly lastSummary: "Last summary";
|
|
38
|
+
readonly lastNextStep: "Last next step";
|
|
39
|
+
readonly lastError: "Last error";
|
|
40
|
+
readonly noActive: "No grub task is active.";
|
|
41
|
+
readonly noStarted: "No grub task has been started in this session.";
|
|
42
|
+
readonly decision: "Decision";
|
|
43
|
+
readonly summary: "Summary";
|
|
44
|
+
readonly nextStep: "Next step";
|
|
45
|
+
readonly resumeSummary: (id: string, iteration: number, phase: string) => string;
|
|
46
|
+
readonly resumeHint: "Use /grub status to inspect, /grub resume to continue dispatch, or /grub stop to abandon.";
|
|
47
|
+
readonly startingIteration: (iteration: number, id: string) => string;
|
|
48
|
+
readonly startedTask: (id: string) => string;
|
|
49
|
+
readonly initPhase: "Init phase: expand feature-list.json / init.sh / progress-log.md before broad implementation.";
|
|
50
|
+
readonly safetyLimits: (maxIterations: number, maxFailures: number) => string;
|
|
51
|
+
readonly resuming: (id: string) => string;
|
|
52
|
+
readonly stopped: (id: string) => string;
|
|
53
|
+
readonly noActiveRunning: "No active grub task is running.";
|
|
54
|
+
readonly noPersisted: "No adopted or persisted grub task to resume.";
|
|
55
|
+
readonly failedResume: (id: string, message: string) => string;
|
|
56
|
+
readonly failedAdopt: (message: string) => string;
|
|
57
|
+
readonly failedNoAssistant: "Grub run ended without an assistant message.";
|
|
58
|
+
readonly iterationFailedRetry: (iteration: number | undefined) => string;
|
|
59
|
+
readonly invalidLoopState: "Assistant response did not include a valid <loop-state> block.";
|
|
60
|
+
readonly invalidLoopRetry: (iteration: number | undefined) => string;
|
|
61
|
+
readonly prematureComplete: (reason: string) => string;
|
|
62
|
+
readonly harnessCreated: "- Harness created by /grub.";
|
|
63
|
+
readonly structuredFeatureNote: "- Structured feature list lives in feature-list.json; only passes/evidence may change.";
|
|
64
|
+
readonly initScriptNote: "- init.sh performs get-bearings + smoke before every iteration.";
|
|
65
|
+
readonly iterationsHeading: "## Iterations";
|
|
66
|
+
readonly appendIterationNote: "- (append one short entry per iteration with verification evidence)";
|
|
67
|
+
readonly progressLogTitle: (id: string) => string;
|
|
68
|
+
readonly initializationHeading: "## Initialization";
|
|
69
|
+
};
|
|
70
|
+
readonly zh: {
|
|
71
|
+
readonly prefix: "[Grub]";
|
|
72
|
+
readonly missingGoal: "缺少 grub 目标。";
|
|
73
|
+
readonly usage: readonly ["[Grub] 用法:", " /grub <目标> [--max-iter N] [--max-fail N] 启动一个自主长任务", " /grub status [--json] 查看当前或最近结束的任务", " /grub resume 继续磁盘中恢复的任务", " /grub stop 停止当前任务", "", "[Grub] 任务产物位于 .grub/<task-id>/:", " feature-list.json 结构化功能清单(agent 只能修改 passes/evidence)", " progress-log.md 追加式进度记录", " init.sh 每轮开始前的环境定位和烟测脚本", " state.json 持久化控制器状态(用于跨会话恢复)", "", "[Grub] agent 会持续迭代,直到完成、阻塞、用户停止,或触发安全上限。", "只有 feature-list.json 中所有功能都 passes:true 时,才允许声明完成。"];
|
|
74
|
+
readonly activeTask: "当前任务";
|
|
75
|
+
readonly lastTask: "最近任务";
|
|
76
|
+
readonly status: "状态";
|
|
77
|
+
readonly phase: "阶段";
|
|
78
|
+
readonly goal: "目标";
|
|
79
|
+
readonly started: "开始时间";
|
|
80
|
+
readonly updated: "更新时间";
|
|
81
|
+
readonly currentIteration: "当前轮次";
|
|
82
|
+
readonly completedIterations: "已完成轮次";
|
|
83
|
+
readonly awaitingResult: "等待结果";
|
|
84
|
+
readonly yes: "是";
|
|
85
|
+
readonly no: "否";
|
|
86
|
+
readonly consecutiveFailures: "连续失败";
|
|
87
|
+
readonly maxIterations: "最大轮次";
|
|
88
|
+
readonly harnessDir: "Harness 目录";
|
|
89
|
+
readonly featureList: "功能清单";
|
|
90
|
+
readonly progressLog: "进度日志";
|
|
91
|
+
readonly initScript: "初始化脚本";
|
|
92
|
+
readonly stateFile: "状态文件";
|
|
93
|
+
readonly featuresPassing: (passing: number, total: number) => string;
|
|
94
|
+
readonly lastSummary: "上次总结";
|
|
95
|
+
readonly lastNextStep: "下一步";
|
|
96
|
+
readonly lastError: "最近错误";
|
|
97
|
+
readonly noActive: "当前没有 grub 任务。";
|
|
98
|
+
readonly noStarted: "本会话还没有启动 grub 任务。";
|
|
99
|
+
readonly decision: "决策";
|
|
100
|
+
readonly summary: "总结";
|
|
101
|
+
readonly nextStep: "下一步";
|
|
102
|
+
readonly resumeSummary: (id: string, iteration: number, phase: string) => string;
|
|
103
|
+
readonly resumeHint: "可用 /grub status 查看,/grub resume 继续派发,或 /grub stop 放弃。";
|
|
104
|
+
readonly startingIteration: (iteration: number, id: string) => string;
|
|
105
|
+
readonly startedTask: (id: string) => string;
|
|
106
|
+
readonly initPhase: "初始化阶段:先完善 feature-list.json / init.sh / progress-log.md,再开始大范围实现。";
|
|
107
|
+
readonly safetyLimits: (maxIterations: number, maxFailures: number) => string;
|
|
108
|
+
readonly resuming: (id: string) => string;
|
|
109
|
+
readonly stopped: (id: string) => string;
|
|
110
|
+
readonly noActiveRunning: "当前没有正在运行的 grub 任务。";
|
|
111
|
+
readonly noPersisted: "没有可恢复的 grub 任务。";
|
|
112
|
+
readonly failedResume: (id: string, message: string) => string;
|
|
113
|
+
readonly failedAdopt: (message: string) => string;
|
|
114
|
+
readonly failedNoAssistant: "Grub 本轮结束时没有 assistant 消息。";
|
|
115
|
+
readonly iterationFailedRetry: (iteration: number | undefined) => string;
|
|
116
|
+
readonly invalidLoopState: "Assistant 回复缺少有效的 <loop-state> 块。";
|
|
117
|
+
readonly invalidLoopRetry: (iteration: number | undefined) => string;
|
|
118
|
+
readonly prematureComplete: (reason: string) => string;
|
|
119
|
+
readonly harnessCreated: "- Harness 由 /grub 创建。";
|
|
120
|
+
readonly structuredFeatureNote: "- 结构化功能清单位于 feature-list.json;后续只能修改 passes/evidence。";
|
|
121
|
+
readonly initScriptNote: "- 每轮开始前由 init.sh 执行环境定位和烟测。";
|
|
122
|
+
readonly iterationsHeading: "## 迭代记录";
|
|
123
|
+
readonly appendIterationNote: "- (每轮追加一条简短记录,包含验证证据)";
|
|
124
|
+
readonly progressLogTitle: (id: string) => string;
|
|
125
|
+
readonly initializationHeading: "## 初始化";
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
export {};
|