@astrosheep/keiyaku 0.1.85 → 0.1.87
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 +3 -1
- package/build/.tsbuildinfo +1 -1
- package/build/agents/claude-agent-sdk-exec.js +104 -0
- package/build/agents/codex-cli-exec.js +3 -1
- package/build/agents/codex-sdk-exec.js +75 -0
- package/build/agents/gemini-cli-core-exec.js +1 -1
- package/build/agents/index.js +3 -2
- package/build/agents/progress-reporter.js +137 -0
- package/build/agents/round-runner.js +1 -0
- package/build/cli/index.js +58 -20
- package/build/config/architect-hints.js +15 -12
- package/build/config/env-keys.js +2 -0
- package/build/config/schema.js +3 -0
- package/build/generated/version.js +1 -1
- package/build/git/core.js +0 -1
- package/build/responses/responses.js +21 -36
- package/build/tools/schema.js +1 -0
- package/build/tools/status/read.js +10 -7
- package/build/tools/summon/run.js +1 -0
- package/package.json +2 -2
- package/skills/keiyaku/SKILL.md +4 -3
|
@@ -4,6 +4,7 @@ import { getConfig } from "../config/config.js";
|
|
|
4
4
|
import { DISABLE_SUBAGENT_SPAWN_ENV_KEY } from "../config/env-keys.js";
|
|
5
5
|
import { expandHomePath } from "../config/path-utils.js";
|
|
6
6
|
import { appendDebugLog } from "../telemetry/debug-log.js";
|
|
7
|
+
import { SubagentProgressReporter } from "./progress-reporter.js";
|
|
7
8
|
import { StringTailBuffer } from "./string-tail-buffer.js";
|
|
8
9
|
import { SUBAGENT_SESSION_VERSION, SubagentExecError } from "./types.js";
|
|
9
10
|
const CLAUDE_AGENT_SDK_PROVIDER = "claude-agent-sdk";
|
|
@@ -84,6 +85,102 @@ function extractResultText(event) {
|
|
|
84
85
|
const errorText = event.errors.join("\n").trim();
|
|
85
86
|
return errorText || undefined;
|
|
86
87
|
}
|
|
88
|
+
function readStringProperty(value, key) {
|
|
89
|
+
if (!value || typeof value !== "object")
|
|
90
|
+
return undefined;
|
|
91
|
+
const property = value[key];
|
|
92
|
+
return coerceString(property);
|
|
93
|
+
}
|
|
94
|
+
function readNumberProperty(value, key) {
|
|
95
|
+
if (!value || typeof value !== "object")
|
|
96
|
+
return 0;
|
|
97
|
+
const property = value[key];
|
|
98
|
+
return typeof property === "number" && Number.isFinite(property) ? property : 0;
|
|
99
|
+
}
|
|
100
|
+
function recordAssistantToolUses(reporter, event) {
|
|
101
|
+
let toolUseCount = 0;
|
|
102
|
+
for (const block of event.message.content) {
|
|
103
|
+
if (!("type" in block) || block.type !== "tool_use")
|
|
104
|
+
continue;
|
|
105
|
+
toolUseCount += 1;
|
|
106
|
+
reporter.recordToolOnce(readStringProperty(block, "id"), readStringProperty(block, "name") ?? "tool_use");
|
|
107
|
+
}
|
|
108
|
+
return toolUseCount;
|
|
109
|
+
}
|
|
110
|
+
function recordClaudeProgressEvent(reporter, event) {
|
|
111
|
+
reporter.recordEvent();
|
|
112
|
+
if (event.type === "assistant") {
|
|
113
|
+
const toolUseCount = recordAssistantToolUses(reporter, event);
|
|
114
|
+
if (toolUseCount > 0) {
|
|
115
|
+
reporter.setState("using tools");
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (event.type === "result") {
|
|
120
|
+
reporter.setState(event.subtype === "success" ? "completed" : "failed");
|
|
121
|
+
if (event.subtype !== "success") {
|
|
122
|
+
reporter.recordFailure();
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (event.type === "tool_progress") {
|
|
127
|
+
reporter.recordToolOnce(event.tool_use_id, event.tool_name);
|
|
128
|
+
reporter.setState(`using ${event.tool_name}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (event.type === "tool_use_summary") {
|
|
132
|
+
reporter.setSummary(event.summary);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (event.type !== "system")
|
|
136
|
+
return;
|
|
137
|
+
switch (event.subtype) {
|
|
138
|
+
case "init":
|
|
139
|
+
reporter.setState("initialized");
|
|
140
|
+
break;
|
|
141
|
+
case "session_state_changed":
|
|
142
|
+
reporter.setState(event.state);
|
|
143
|
+
break;
|
|
144
|
+
case "status":
|
|
145
|
+
reporter.setState(event.status ?? "running");
|
|
146
|
+
break;
|
|
147
|
+
case "task_started":
|
|
148
|
+
reporter.setState("task started");
|
|
149
|
+
reporter.setSummary(event.description);
|
|
150
|
+
break;
|
|
151
|
+
case "task_progress": {
|
|
152
|
+
const lastToolName = readStringProperty(event, "last_tool_name");
|
|
153
|
+
if (lastToolName) {
|
|
154
|
+
reporter.setState(`using ${lastToolName}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
reporter.setState("task running");
|
|
158
|
+
}
|
|
159
|
+
reporter.setSummary(readStringProperty(event, "summary") ?? readStringProperty(event, "description"));
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "task_notification":
|
|
163
|
+
reporter.setState(readStringProperty(event, "status"));
|
|
164
|
+
reporter.setSummary(readStringProperty(event, "summary"));
|
|
165
|
+
if (readStringProperty(event, "status") === "failed") {
|
|
166
|
+
reporter.recordFailure();
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
case "hook_started":
|
|
170
|
+
reporter.setState(`hook ${event.hook_event}`);
|
|
171
|
+
break;
|
|
172
|
+
case "hook_response":
|
|
173
|
+
if (event.outcome === "error")
|
|
174
|
+
reporter.recordFailure();
|
|
175
|
+
reporter.setState(`hook ${event.outcome}`);
|
|
176
|
+
break;
|
|
177
|
+
default: {
|
|
178
|
+
const changedFileCount = readNumberProperty(event, "file_count");
|
|
179
|
+
reporter.recordChangedFiles(changedFileCount);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
87
184
|
async function loadClaudeAgentSdk() {
|
|
88
185
|
if (claudeAgentSdkLoader) {
|
|
89
186
|
return claudeAgentSdkLoader();
|
|
@@ -109,6 +206,11 @@ export async function runClaudeAgentSdkExec(prompt, cwd, options) {
|
|
|
109
206
|
let finalMessage;
|
|
110
207
|
let timedOut = false;
|
|
111
208
|
let idleTimedOut = false;
|
|
209
|
+
const progressReporter = new SubagentProgressReporter({
|
|
210
|
+
provider: CLAUDE_AGENT_SDK_PROVIDER,
|
|
211
|
+
cwd,
|
|
212
|
+
intervalMin: subagent.progressIntervalMin,
|
|
213
|
+
});
|
|
112
214
|
const timeoutController = new AbortController();
|
|
113
215
|
const timeout = setTimeout(() => {
|
|
114
216
|
timedOut = true;
|
|
@@ -165,6 +267,7 @@ export async function runClaudeAgentSdkExec(prompt, cwd, options) {
|
|
|
165
267
|
for await (const event of streamed) {
|
|
166
268
|
scheduleIdleTimeout();
|
|
167
269
|
outputBuffer.append(`${JSON.stringify(event)}\n`);
|
|
270
|
+
recordClaudeProgressEvent(progressReporter, event);
|
|
168
271
|
resumedSessionId = extractResumeToken(event) ?? resumedSessionId;
|
|
169
272
|
switch (event.type) {
|
|
170
273
|
case "assistant": {
|
|
@@ -243,6 +346,7 @@ export async function runClaudeAgentSdkExec(prompt, cwd, options) {
|
|
|
243
346
|
clearTimeout(timeout);
|
|
244
347
|
if (idleTimeout)
|
|
245
348
|
clearTimeout(idleTimeout);
|
|
349
|
+
progressReporter.stop();
|
|
246
350
|
unlinkAbort?.();
|
|
247
351
|
}
|
|
248
352
|
}
|
|
@@ -166,7 +166,9 @@ export async function runCodexCliExec(prompt, cwd, options = {}) {
|
|
|
166
166
|
const idleTimeoutMs = subagent.execIdleTimeoutMs;
|
|
167
167
|
const maxCaptureChars = codex.execMaxCaptureChars;
|
|
168
168
|
const threadId = options.threadId?.trim();
|
|
169
|
-
const codexArgs = threadId
|
|
169
|
+
const codexArgs = threadId
|
|
170
|
+
? ["exec", "resume", "--full-auto", "--json", "--skip-git-repo-check"]
|
|
171
|
+
: ["exec", "--full-auto", "--json", "--skip-git-repo-check", "-C", cwd];
|
|
170
172
|
if (options.model) {
|
|
171
173
|
codexArgs.push("-m", options.model);
|
|
172
174
|
}
|
|
@@ -3,6 +3,7 @@ import { CODEX_MODEL_REASONING_EFFORT_CONFIG_KEY } from "../keiyaku.js";
|
|
|
3
3
|
import { getConfig } from "../config/config.js";
|
|
4
4
|
import { DISABLE_SUBAGENT_SPAWN_ENV_KEY } from "../config/env-keys.js";
|
|
5
5
|
import { appendDebugBlock, appendDebugLog } from "../telemetry/debug-log.js";
|
|
6
|
+
import { SubagentProgressReporter } from "./progress-reporter.js";
|
|
6
7
|
import { StringTailBuffer } from "./string-tail-buffer.js";
|
|
7
8
|
import { SUBAGENT_SESSION_VERSION, SubagentExecError } from "./types.js";
|
|
8
9
|
const CODEX_EVENT_THREAD_STARTED = "thread.started";
|
|
@@ -31,6 +32,72 @@ function extractAgentMessage(event) {
|
|
|
31
32
|
}
|
|
32
33
|
return event.item.type === "agent_message" ? coerceString(event.item.text) : undefined;
|
|
33
34
|
}
|
|
35
|
+
function summarizeCommand(command) {
|
|
36
|
+
return command.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
37
|
+
}
|
|
38
|
+
function recordCodexProgressEvent(reporter, event) {
|
|
39
|
+
reporter.recordEvent();
|
|
40
|
+
switch (event.type) {
|
|
41
|
+
case CODEX_EVENT_TURN_STARTED:
|
|
42
|
+
reporter.setState("running");
|
|
43
|
+
break;
|
|
44
|
+
case "turn.completed":
|
|
45
|
+
reporter.setState("completed");
|
|
46
|
+
break;
|
|
47
|
+
case CODEX_EVENT_TURN_FAILED:
|
|
48
|
+
case CODEX_EVENT_ERROR:
|
|
49
|
+
reporter.setState("failed");
|
|
50
|
+
reporter.recordFailure();
|
|
51
|
+
break;
|
|
52
|
+
case "item.started":
|
|
53
|
+
if (event.item.type === "command_execution") {
|
|
54
|
+
reporter.recordToolOnce(event.item.id, "shell", summarizeCommand(event.item.command));
|
|
55
|
+
reporter.setState("running command");
|
|
56
|
+
}
|
|
57
|
+
else if (event.item.type === "mcp_tool_call") {
|
|
58
|
+
reporter.recordToolOnce(event.item.id, `${event.item.server}.${event.item.tool}`);
|
|
59
|
+
reporter.setState("using mcp tool");
|
|
60
|
+
}
|
|
61
|
+
else if (event.item.type === "web_search") {
|
|
62
|
+
reporter.recordToolOnce(event.item.id, "web_search");
|
|
63
|
+
reporter.setState("searching");
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case CODEX_EVENT_ITEM_COMPLETED:
|
|
67
|
+
if (event.item.type === "file_change") {
|
|
68
|
+
reporter.recordToolOnce(event.item.id, "file_change", `${event.item.changes.length} file(s)`);
|
|
69
|
+
reporter.recordChangedFiles(event.item.changes.length);
|
|
70
|
+
reporter.setState(event.item.status === "failed" ? "file change failed" : "changed files");
|
|
71
|
+
if (event.item.status === "failed")
|
|
72
|
+
reporter.recordFailure();
|
|
73
|
+
}
|
|
74
|
+
else if (event.item.type === "command_execution") {
|
|
75
|
+
reporter.recordToolOnce(event.item.id, "shell", summarizeCommand(event.item.command));
|
|
76
|
+
if (event.item.status === "failed")
|
|
77
|
+
reporter.recordFailure();
|
|
78
|
+
reporter.setState(event.item.status === "failed" ? "command failed" : "command completed");
|
|
79
|
+
}
|
|
80
|
+
else if (event.item.type === "mcp_tool_call") {
|
|
81
|
+
reporter.recordToolOnce(event.item.id, `${event.item.server}.${event.item.tool}`);
|
|
82
|
+
if (event.item.status === "failed")
|
|
83
|
+
reporter.recordFailure();
|
|
84
|
+
reporter.setState(event.item.status === "failed" ? "mcp tool failed" : "mcp tool completed");
|
|
85
|
+
}
|
|
86
|
+
else if (event.item.type === "web_search") {
|
|
87
|
+
reporter.recordToolOnce(event.item.id, "web_search");
|
|
88
|
+
reporter.setState("search completed");
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case CODEX_EVENT_ITEM_UPDATED:
|
|
92
|
+
if (event.item.type === "todo_list") {
|
|
93
|
+
const completed = event.item.items.filter((item) => item.completed).length;
|
|
94
|
+
reporter.setSummary(`todo ${completed}/${event.item.items.length} complete`);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
34
101
|
function appendEventType(recordedTypes, eventType) {
|
|
35
102
|
if (recordedTypes.length === MAX_RECORDED_EVENT_TYPES) {
|
|
36
103
|
recordedTypes.shift();
|
|
@@ -80,6 +147,11 @@ export async function runCodexSdkExec(prompt, cwd, options = {}) {
|
|
|
80
147
|
let streamErrorMessage;
|
|
81
148
|
let timedOut = false;
|
|
82
149
|
let idleTimedOut = false;
|
|
150
|
+
const progressReporter = new SubagentProgressReporter({
|
|
151
|
+
provider: CODEX_SDK_PROVIDER,
|
|
152
|
+
cwd,
|
|
153
|
+
intervalMin: subagent.progressIntervalMin,
|
|
154
|
+
});
|
|
83
155
|
const timeoutController = new AbortController();
|
|
84
156
|
const timeout = setTimeout(() => {
|
|
85
157
|
timedOut = true;
|
|
@@ -117,6 +189,7 @@ export async function runCodexSdkExec(prompt, cwd, options = {}) {
|
|
|
117
189
|
const threadOptions = {
|
|
118
190
|
model: options.model,
|
|
119
191
|
workingDirectory: cwd,
|
|
192
|
+
skipGitRepoCheck: true,
|
|
120
193
|
};
|
|
121
194
|
const thread = threadId ? sdk.resumeThread(threadId, threadOptions) : sdk.startThread(threadOptions);
|
|
122
195
|
const streamed = await thread.runStreamed(prompt, { signal: timeoutController.signal });
|
|
@@ -124,6 +197,7 @@ export async function runCodexSdkExec(prompt, cwd, options = {}) {
|
|
|
124
197
|
totalEventCount += 1;
|
|
125
198
|
appendEventType(recordedEventTypes, event.type);
|
|
126
199
|
stdoutBuffer.append(`${JSON.stringify(event)}\n`);
|
|
200
|
+
recordCodexProgressEvent(progressReporter, event);
|
|
127
201
|
scheduleIdleTimeout();
|
|
128
202
|
if (event.type === CODEX_EVENT_THREAD_STARTED) {
|
|
129
203
|
resumeToken = event.thread_id;
|
|
@@ -211,6 +285,7 @@ export async function runCodexSdkExec(prompt, cwd, options = {}) {
|
|
|
211
285
|
clearTimeout(timeout);
|
|
212
286
|
if (idleTimeout)
|
|
213
287
|
clearTimeout(idleTimeout);
|
|
288
|
+
progressReporter.stop();
|
|
214
289
|
unlinkAbort?.();
|
|
215
290
|
}
|
|
216
291
|
}
|
|
@@ -205,7 +205,7 @@ async function loadPersistedGeminiSession(module, cwd, sessionId) {
|
|
|
205
205
|
}
|
|
206
206
|
function createGeminiCliCoreConfig(module, cwd, sessionId, model) {
|
|
207
207
|
const configParameters = {
|
|
208
|
-
allowedEnvironmentVariables:
|
|
208
|
+
allowedEnvironmentVariables: Object.keys(process.env),
|
|
209
209
|
approvalMode: module.ApprovalMode.YOLO,
|
|
210
210
|
cwd,
|
|
211
211
|
debugMode: false,
|
package/build/agents/index.js
CHANGED
|
@@ -213,10 +213,11 @@ export async function resolveSubagentConfig(agentName, cwd) {
|
|
|
213
213
|
}
|
|
214
214
|
export async function runSubagentExec(agentName, prompt, cwd, options = {}) {
|
|
215
215
|
const config = await resolveSubagentConfig(agentName, cwd);
|
|
216
|
+
const effectiveConfig = options.model ? { ...config, model: options.model } : config;
|
|
216
217
|
const adapter = SUBAGENT_ADAPTERS[config.provider];
|
|
217
218
|
if (!options.resume) {
|
|
218
|
-
return adapter.run(prompt, cwd, { ...
|
|
219
|
+
return adapter.run(prompt, cwd, { ...effectiveConfig, signal: options.signal });
|
|
219
220
|
}
|
|
220
221
|
const handle = validateResumeSession(adapter, options.resume.session, options.resume.resumePath);
|
|
221
|
-
return adapter.resume(prompt, cwd, { ...
|
|
222
|
+
return adapter.resume(prompt, cwd, { ...effectiveConfig, signal: options.signal }, handle);
|
|
222
223
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { logInfo } from "../telemetry/logger.js";
|
|
2
|
+
function createWindow(now) {
|
|
3
|
+
return {
|
|
4
|
+
startedAt: now,
|
|
5
|
+
eventCount: 0,
|
|
6
|
+
toolCounts: new Map(),
|
|
7
|
+
changedFileCount: 0,
|
|
8
|
+
failureCount: 0,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function formatDuration(ms) {
|
|
12
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
13
|
+
if (seconds < 60)
|
|
14
|
+
return `${seconds}s`;
|
|
15
|
+
const minutes = Math.floor(seconds / 60);
|
|
16
|
+
const remainingSeconds = seconds % 60;
|
|
17
|
+
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
|
18
|
+
}
|
|
19
|
+
function formatToolCounts(toolCounts) {
|
|
20
|
+
if (toolCounts.size === 0)
|
|
21
|
+
return "none";
|
|
22
|
+
return [...toolCounts.entries()]
|
|
23
|
+
.sort(([leftName, leftCount], [rightName, rightCount]) => rightCount - leftCount || leftName.localeCompare(rightName))
|
|
24
|
+
.map(([name, count]) => `${name} x${count}`)
|
|
25
|
+
.join(", ");
|
|
26
|
+
}
|
|
27
|
+
function sanitizeSummary(summary) {
|
|
28
|
+
return summary.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
29
|
+
}
|
|
30
|
+
export class SubagentProgressReporter {
|
|
31
|
+
options;
|
|
32
|
+
window = createWindow(Date.now());
|
|
33
|
+
recordedToolKeys = new Set();
|
|
34
|
+
timer;
|
|
35
|
+
sawFirstEvent = false;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.options = options;
|
|
38
|
+
if (options.intervalMin > 0) {
|
|
39
|
+
this.timer = setInterval(() => this.flush(), options.intervalMin * 60 * 1000);
|
|
40
|
+
this.timer.unref?.();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
recordEvent() {
|
|
44
|
+
this.window.eventCount += 1;
|
|
45
|
+
if (!this.sawFirstEvent) {
|
|
46
|
+
this.sawFirstEvent = true;
|
|
47
|
+
this.logFirstEvent();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
recordTool(name, detail) {
|
|
51
|
+
const normalized = name?.trim() || "unknown";
|
|
52
|
+
this.window.toolCounts.set(normalized, (this.window.toolCounts.get(normalized) ?? 0) + 1);
|
|
53
|
+
this.setLastTool(normalized, detail);
|
|
54
|
+
}
|
|
55
|
+
recordToolOnce(key, name, detail) {
|
|
56
|
+
const normalizedKey = key?.trim();
|
|
57
|
+
if (!normalizedKey) {
|
|
58
|
+
this.recordTool(name, detail);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (this.recordedToolKeys.has(normalizedKey))
|
|
62
|
+
return;
|
|
63
|
+
this.recordedToolKeys.add(normalizedKey);
|
|
64
|
+
this.recordTool(name, detail);
|
|
65
|
+
}
|
|
66
|
+
recordChangedFiles(count) {
|
|
67
|
+
if (count > 0) {
|
|
68
|
+
this.window.changedFileCount += count;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
recordFailure() {
|
|
72
|
+
this.window.failureCount += 1;
|
|
73
|
+
}
|
|
74
|
+
setState(state) {
|
|
75
|
+
if (state?.trim()) {
|
|
76
|
+
this.window.lastState = state.trim();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
setSummary(summary) {
|
|
80
|
+
const normalized = summary ? sanitizeSummary(summary) : "";
|
|
81
|
+
if (normalized) {
|
|
82
|
+
this.window.lastSummary = normalized;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
setLastTool(name, detail) {
|
|
86
|
+
const normalizedDetail = detail ? sanitizeSummary(detail) : "";
|
|
87
|
+
this.window.lastTool = normalizedDetail ? `${name}: ${normalizedDetail}` : name;
|
|
88
|
+
}
|
|
89
|
+
flush() {
|
|
90
|
+
if (this.options.intervalMin <= 0)
|
|
91
|
+
return;
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
const elapsedMs = now - this.window.startedAt;
|
|
94
|
+
const hasActivity = this.window.eventCount > 0 ||
|
|
95
|
+
this.window.toolCounts.size > 0 ||
|
|
96
|
+
this.window.changedFileCount > 0 ||
|
|
97
|
+
this.window.failureCount > 0 ||
|
|
98
|
+
this.window.lastState !== undefined ||
|
|
99
|
+
this.window.lastSummary !== undefined;
|
|
100
|
+
if (!hasActivity) {
|
|
101
|
+
this.window = createWindow(now);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const details = [
|
|
105
|
+
`tools: ${formatToolCounts(this.window.toolCounts)}`,
|
|
106
|
+
];
|
|
107
|
+
if (this.window.changedFileCount > 0)
|
|
108
|
+
details.push(`changed files: ${this.window.changedFileCount}`);
|
|
109
|
+
if (this.window.failureCount > 0)
|
|
110
|
+
details.push(`failures: ${this.window.failureCount}`);
|
|
111
|
+
if (this.window.lastTool)
|
|
112
|
+
details.push(`last tool: ${this.window.lastTool}`);
|
|
113
|
+
if (this.window.lastState)
|
|
114
|
+
details.push(`state: ${this.window.lastState}`);
|
|
115
|
+
if (this.window.lastSummary)
|
|
116
|
+
details.push(`summary: ${this.window.lastSummary}`);
|
|
117
|
+
logInfo(`[${this.options.provider}] activity last ${formatDuration(elapsedMs)}: ${details.join("; ")}`, {
|
|
118
|
+
cwd: this.options.cwd,
|
|
119
|
+
section: this.options.provider,
|
|
120
|
+
progressOnly: true,
|
|
121
|
+
});
|
|
122
|
+
this.window = createWindow(now);
|
|
123
|
+
}
|
|
124
|
+
stop() {
|
|
125
|
+
if (this.timer)
|
|
126
|
+
clearInterval(this.timer);
|
|
127
|
+
}
|
|
128
|
+
logFirstEvent() {
|
|
129
|
+
if (this.options.intervalMin <= 0)
|
|
130
|
+
return;
|
|
131
|
+
logInfo(`[${this.options.provider}] stream connected; activity summaries every ${this.options.intervalMin}m`, {
|
|
132
|
+
cwd: this.options.cwd,
|
|
133
|
+
section: this.options.provider,
|
|
134
|
+
progressOnly: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -40,6 +40,7 @@ export async function runSubagentWithResult(subagent, prompt, cwd, round, option
|
|
|
40
40
|
const result = await runSubagentExec(subagent.name, prompt, cwd, {
|
|
41
41
|
signal: options.signal,
|
|
42
42
|
resume: options.resume,
|
|
43
|
+
model: options.model,
|
|
43
44
|
});
|
|
44
45
|
const summary = result.finalMessage || `${identity} completed successfully.`;
|
|
45
46
|
if (result.diagnostics.trim()) {
|
package/build/cli/index.js
CHANGED
|
@@ -35,6 +35,11 @@ const FLAG_SPECS = {
|
|
|
35
35
|
placeholder: "NAME",
|
|
36
36
|
description: `Start a new ${ACTOR_IDENTITY} session with the configured agent name.`,
|
|
37
37
|
},
|
|
38
|
+
model: {
|
|
39
|
+
kind: "value",
|
|
40
|
+
placeholder: "MODEL",
|
|
41
|
+
description: "Override the configured model for this agent run.",
|
|
42
|
+
},
|
|
38
43
|
incognito: {
|
|
39
44
|
kind: "boolean",
|
|
40
45
|
description: "Do not persist response history and do not allow resume.",
|
|
@@ -182,6 +187,7 @@ function buildSummonInput(content, flags) {
|
|
|
182
187
|
title: titleFromSummonPrompt(task),
|
|
183
188
|
task,
|
|
184
189
|
agent: flags.agent,
|
|
190
|
+
model: flags.model,
|
|
185
191
|
resumePath: flags.resumePath,
|
|
186
192
|
incognito: flags.incognito,
|
|
187
193
|
};
|
|
@@ -313,6 +319,7 @@ const CLI_COMMAND_ORDER = [
|
|
|
313
319
|
"bind",
|
|
314
320
|
"round open",
|
|
315
321
|
"summon",
|
|
322
|
+
"ask",
|
|
316
323
|
"resume",
|
|
317
324
|
"zako list",
|
|
318
325
|
"zako ls",
|
|
@@ -425,12 +432,11 @@ const CLI_COMMAND_SPECS = {
|
|
|
425
432
|
command: "summon",
|
|
426
433
|
summary: `Summon a ${ACTOR_IDENTITY} to help the caller with bounded work`,
|
|
427
434
|
usage: [
|
|
428
|
-
"keiyaku summon [--cwd DIR] <name> [prompt]",
|
|
429
|
-
"keiyaku summon [--cwd DIR] <name> - < prompt.md",
|
|
430
|
-
"keiyaku summon [--cwd DIR] --agent NAME [prompt]",
|
|
435
|
+
"keiyaku summon [--cwd DIR] [--model MODEL] <name> [prompt]",
|
|
436
|
+
"keiyaku summon [--cwd DIR] [--model MODEL] <name> - < prompt.md",
|
|
431
437
|
],
|
|
432
438
|
stdin: "none",
|
|
433
|
-
flags: ["cwd", "agent", "incognito"],
|
|
439
|
+
flags: ["cwd", "agent", "model", "incognito"],
|
|
434
440
|
purpose: `Summon a ${ACTOR_IDENTITY} to help the caller/main agent with bounded work, inside or outside a Keiyaku.`,
|
|
435
441
|
input: [
|
|
436
442
|
"<plain prompt text>",
|
|
@@ -445,20 +451,45 @@ const CLI_COMMAND_SPECS = {
|
|
|
445
451
|
],
|
|
446
452
|
examples: [
|
|
447
453
|
"keiyaku summon femini \"Review this diff\"",
|
|
448
|
-
"keiyaku summon --agent femini \"Review this diff\"",
|
|
449
454
|
"keiyaku summon femini - < prompt.md",
|
|
450
455
|
],
|
|
451
456
|
run: async (stdin, flags, signal) => await createSummonHandler()(buildSummonInput(stdin, flags), { signal }),
|
|
452
457
|
},
|
|
458
|
+
ask: {
|
|
459
|
+
command: "ask",
|
|
460
|
+
summary: `Alias for summon; ask a ${ACTOR_IDENTITY} for bounded help`,
|
|
461
|
+
usage: [
|
|
462
|
+
"keiyaku ask [--cwd DIR] [--model MODEL] <name> [prompt]",
|
|
463
|
+
"keiyaku ask [--cwd DIR] [--model MODEL] <name> - < prompt.md",
|
|
464
|
+
],
|
|
465
|
+
stdin: "none",
|
|
466
|
+
flags: ["cwd", "agent", "model", "incognito"],
|
|
467
|
+
purpose: `Alias for summon. Ask a ${ACTOR_IDENTITY} to help with bounded work, inside or outside a Keiyaku.`,
|
|
468
|
+
input: [
|
|
469
|
+
"<plain prompt text>",
|
|
470
|
+
"Use `-` or omit prompt to read plain prompt text from stdin.",
|
|
471
|
+
],
|
|
472
|
+
notes: [
|
|
473
|
+
"Equivalent to `keiyaku summon NAME [prompt|-]`.",
|
|
474
|
+
"Does not require an active Keiyaku; it can be used outside the round lifecycle.",
|
|
475
|
+
`If a round is open, the ${ACTOR_IDENTITY} receives the round scope automatically.`,
|
|
476
|
+
"Warning: `--incognito` runs a one-off ask: no response history is persisted, so this session cannot be restored later with `keiyaku resume`.",
|
|
477
|
+
],
|
|
478
|
+
examples: [
|
|
479
|
+
"keiyaku ask femini \"Review this diff\"",
|
|
480
|
+
"keiyaku ask femini - < prompt.md",
|
|
481
|
+
],
|
|
482
|
+
run: async (stdin, flags, signal) => await createSummonHandler()(buildSummonInput(stdin, flags), { signal }),
|
|
483
|
+
},
|
|
453
484
|
resume: {
|
|
454
485
|
command: "resume",
|
|
455
486
|
summary: `Resume a prior ${ACTOR_IDENTITY} session from a response artifact`,
|
|
456
487
|
usage: [
|
|
457
|
-
"keiyaku resume [--cwd DIR] <response-path> [prompt]",
|
|
458
|
-
"keiyaku resume [--cwd DIR] <response-path> - < prompt.md",
|
|
488
|
+
"keiyaku resume [--cwd DIR] [--model MODEL] <response-path> [prompt]",
|
|
489
|
+
"keiyaku resume [--cwd DIR] [--model MODEL] <response-path> - < prompt.md",
|
|
459
490
|
],
|
|
460
491
|
stdin: "none",
|
|
461
|
-
flags: ["cwd"],
|
|
492
|
+
flags: ["cwd", "model"],
|
|
462
493
|
purpose: `Continue the ${ACTOR_IDENTITY} session recorded in a prior response artifact.`,
|
|
463
494
|
input: [
|
|
464
495
|
"<plain prompt text>",
|
|
@@ -497,7 +528,7 @@ const CLI_COMMAND_SPECS = {
|
|
|
497
528
|
stdin: "none",
|
|
498
529
|
flags: ["cwd"],
|
|
499
530
|
purpose: "List built-in and configured Zako names with their description, provider, and executable.",
|
|
500
|
-
notes: ["Use a listed name with `keiyaku summon NAME
|
|
531
|
+
notes: ["Use a listed name with `keiyaku summon NAME`."],
|
|
501
532
|
examples: ["keiyaku zako list", "keiyaku zako ls"],
|
|
502
533
|
run: async (_stdin, flags) => {
|
|
503
534
|
const settings = await loadKeiyakuSettings(flags.cwd ?? process.cwd());
|
|
@@ -511,7 +542,7 @@ const CLI_COMMAND_SPECS = {
|
|
|
511
542
|
stdin: "none",
|
|
512
543
|
flags: ["cwd"],
|
|
513
544
|
purpose: "Alias for zako list.",
|
|
514
|
-
notes: ["Use a listed name with `keiyaku summon NAME
|
|
545
|
+
notes: ["Use a listed name with `keiyaku summon NAME`."],
|
|
515
546
|
examples: ["keiyaku zako ls"],
|
|
516
547
|
run: async (_stdin, flags) => {
|
|
517
548
|
const settings = await loadKeiyakuSettings(flags.cwd ?? process.cwd());
|
|
@@ -673,17 +704,20 @@ function commandRequiresStdin(command) {
|
|
|
673
704
|
function commandSavesFailedStdin(command) {
|
|
674
705
|
return commandRequiresStdin(command) && command !== "summon";
|
|
675
706
|
}
|
|
707
|
+
function isSummonCommand(command) {
|
|
708
|
+
return command === "summon" || command === "ask";
|
|
709
|
+
}
|
|
676
710
|
function commandPositionalRange(command) {
|
|
677
711
|
if (command === "zako show")
|
|
678
712
|
return { min: 1, max: 1, label: "<name>" };
|
|
679
713
|
if (command === "resume")
|
|
680
714
|
return { min: 1, max: 2, label: "<response-path>" };
|
|
681
|
-
if (command
|
|
715
|
+
if (isSummonCommand(command))
|
|
682
716
|
return { min: 0, max: 1 };
|
|
683
717
|
return { min: 0, max: 0 };
|
|
684
718
|
}
|
|
685
719
|
function commandUsesPromptInput(command) {
|
|
686
|
-
return command
|
|
720
|
+
return isSummonCommand(command) || command === "resume";
|
|
687
721
|
}
|
|
688
722
|
function commandTokens(command) {
|
|
689
723
|
return command.split(" ");
|
|
@@ -699,6 +733,9 @@ function assignFlag(flags, name, value) {
|
|
|
699
733
|
case "agent":
|
|
700
734
|
flags.agent = String(value);
|
|
701
735
|
return;
|
|
736
|
+
case "model":
|
|
737
|
+
flags.model = String(value);
|
|
738
|
+
return;
|
|
702
739
|
case "incognito":
|
|
703
740
|
flags.incognito = Boolean(value);
|
|
704
741
|
return;
|
|
@@ -791,25 +828,25 @@ export function parseCliArgs(tokens) {
|
|
|
791
828
|
assignFlag(flags, name, true);
|
|
792
829
|
}
|
|
793
830
|
}
|
|
794
|
-
if (command
|
|
831
|
+
if (isSummonCommand(command)) {
|
|
795
832
|
if (flags.agent) {
|
|
796
833
|
if (positional.length > 1) {
|
|
797
|
-
throw new FlowError("EMPTY_PARAM",
|
|
834
|
+
throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument when --agent is used`);
|
|
798
835
|
}
|
|
799
836
|
}
|
|
800
837
|
else {
|
|
801
838
|
const [name] = positional;
|
|
802
839
|
if (!name) {
|
|
803
|
-
throw new FlowError("EMPTY_PARAM",
|
|
840
|
+
throw new FlowError("EMPTY_PARAM", `${command} requires an agent name; use \`keiyaku ${command} NAME [prompt|-]\``);
|
|
804
841
|
}
|
|
805
842
|
const parsedName = agentNameSchema.safeParse(name);
|
|
806
843
|
if (!parsedName.success) {
|
|
807
|
-
throw new FlowError("EMPTY_PARAM",
|
|
844
|
+
throw new FlowError("EMPTY_PARAM", `${command} requires a valid agent name before the optional prompt`);
|
|
808
845
|
}
|
|
809
846
|
flags.agent = parsedName.data;
|
|
810
847
|
positional.shift();
|
|
811
848
|
if (positional.length > 1) {
|
|
812
|
-
throw new FlowError("EMPTY_PARAM",
|
|
849
|
+
throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument after the agent name`);
|
|
813
850
|
}
|
|
814
851
|
}
|
|
815
852
|
}
|
|
@@ -902,7 +939,7 @@ async function runResponse(command, flags, stdin, positional) {
|
|
|
902
939
|
return await getCliCommandSpec(command).run(stdin, flags, signal, positional);
|
|
903
940
|
}
|
|
904
941
|
function positionalPrompt(command, positional) {
|
|
905
|
-
if (command
|
|
942
|
+
if (isSummonCommand(command))
|
|
906
943
|
return positional[0];
|
|
907
944
|
if (command === "resume")
|
|
908
945
|
return positional[1];
|
|
@@ -1061,12 +1098,13 @@ export function renderCliHelp(version) {
|
|
|
1061
1098
|
" 工作流细节看 guide,命令格式看 --help",
|
|
1062
1099
|
"",
|
|
1063
1100
|
"Flow:",
|
|
1064
|
-
" bind -> round open -> [summon | amend | delivery work]* -> delivery commits -> round close -> [round open | amend | petition claim | petition forfeit]",
|
|
1101
|
+
" bind -> round open -> [summon/ask | amend | delivery work]* -> delivery commits -> round close -> [round open | amend | petition claim | petition forfeit]",
|
|
1065
1102
|
"",
|
|
1066
1103
|
"Commands:",
|
|
1067
1104
|
" bind 和Keiyaku签contract,创建keiyaku branch和KEIYAKU.md。内容确定了你就是contract的dog! 跪下!",
|
|
1068
1105
|
" round open 宣言这个round要干嘛。什么? 还没想好? 那签contract的时候在干嘛??",
|
|
1069
1106
|
" summon 召唤一只Zako来帮你干活。用完就扔 GWAHAHAHA!!",
|
|
1107
|
+
" ask summon的短alias。日常想少打几个字就用这个",
|
|
1070
1108
|
" resume 从response artifact恢复之前的Zako session",
|
|
1071
1109
|
" zako list|ls 看现在能召唤哪些Zako。别连手牌都不知道就开打",
|
|
1072
1110
|
" zako show 看某只Zako的provider/executable/settings。召唤前先验货",
|
|
@@ -1081,7 +1119,7 @@ export function renderCliHelp(version) {
|
|
|
1081
1119
|
"",
|
|
1082
1120
|
"Input:",
|
|
1083
1121
|
" bind, round open, round close, amend, petition claim 从stdin吃Markdown",
|
|
1084
|
-
" summon, resume 吃prompt参数;prompt是 - 或省略时从stdin读",
|
|
1122
|
+
" summon, ask, resume 吃prompt参数;prompt是 - 或省略时从stdin读",
|
|
1085
1123
|
" petition forfeit, zako list|ls, zako show, harness install, guide, status, dump-env 不吃stdin",
|
|
1086
1124
|
" 别再来问了。要精确Markdown sections就自己看 `keiyaku <command> --help`",
|
|
1087
1125
|
].join("\n");
|