@astrosheep/keiyaku 0.1.86 → 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 +56 -16
- 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,11 +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",
|
|
435
|
+
"keiyaku summon [--cwd DIR] [--model MODEL] <name> [prompt]",
|
|
436
|
+
"keiyaku summon [--cwd DIR] [--model MODEL] <name> - < prompt.md",
|
|
430
437
|
],
|
|
431
438
|
stdin: "none",
|
|
432
|
-
flags: ["cwd", "agent", "incognito"],
|
|
439
|
+
flags: ["cwd", "agent", "model", "incognito"],
|
|
433
440
|
purpose: `Summon a ${ACTOR_IDENTITY} to help the caller/main agent with bounded work, inside or outside a Keiyaku.`,
|
|
434
441
|
input: [
|
|
435
442
|
"<plain prompt text>",
|
|
@@ -448,15 +455,41 @@ const CLI_COMMAND_SPECS = {
|
|
|
448
455
|
],
|
|
449
456
|
run: async (stdin, flags, signal) => await createSummonHandler()(buildSummonInput(stdin, flags), { signal }),
|
|
450
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
|
+
},
|
|
451
484
|
resume: {
|
|
452
485
|
command: "resume",
|
|
453
486
|
summary: `Resume a prior ${ACTOR_IDENTITY} session from a response artifact`,
|
|
454
487
|
usage: [
|
|
455
|
-
"keiyaku resume [--cwd DIR] <response-path> [prompt]",
|
|
456
|
-
"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",
|
|
457
490
|
],
|
|
458
491
|
stdin: "none",
|
|
459
|
-
flags: ["cwd"],
|
|
492
|
+
flags: ["cwd", "model"],
|
|
460
493
|
purpose: `Continue the ${ACTOR_IDENTITY} session recorded in a prior response artifact.`,
|
|
461
494
|
input: [
|
|
462
495
|
"<plain prompt text>",
|
|
@@ -671,17 +704,20 @@ function commandRequiresStdin(command) {
|
|
|
671
704
|
function commandSavesFailedStdin(command) {
|
|
672
705
|
return commandRequiresStdin(command) && command !== "summon";
|
|
673
706
|
}
|
|
707
|
+
function isSummonCommand(command) {
|
|
708
|
+
return command === "summon" || command === "ask";
|
|
709
|
+
}
|
|
674
710
|
function commandPositionalRange(command) {
|
|
675
711
|
if (command === "zako show")
|
|
676
712
|
return { min: 1, max: 1, label: "<name>" };
|
|
677
713
|
if (command === "resume")
|
|
678
714
|
return { min: 1, max: 2, label: "<response-path>" };
|
|
679
|
-
if (command
|
|
715
|
+
if (isSummonCommand(command))
|
|
680
716
|
return { min: 0, max: 1 };
|
|
681
717
|
return { min: 0, max: 0 };
|
|
682
718
|
}
|
|
683
719
|
function commandUsesPromptInput(command) {
|
|
684
|
-
return command
|
|
720
|
+
return isSummonCommand(command) || command === "resume";
|
|
685
721
|
}
|
|
686
722
|
function commandTokens(command) {
|
|
687
723
|
return command.split(" ");
|
|
@@ -697,6 +733,9 @@ function assignFlag(flags, name, value) {
|
|
|
697
733
|
case "agent":
|
|
698
734
|
flags.agent = String(value);
|
|
699
735
|
return;
|
|
736
|
+
case "model":
|
|
737
|
+
flags.model = String(value);
|
|
738
|
+
return;
|
|
700
739
|
case "incognito":
|
|
701
740
|
flags.incognito = Boolean(value);
|
|
702
741
|
return;
|
|
@@ -789,25 +828,25 @@ export function parseCliArgs(tokens) {
|
|
|
789
828
|
assignFlag(flags, name, true);
|
|
790
829
|
}
|
|
791
830
|
}
|
|
792
|
-
if (command
|
|
831
|
+
if (isSummonCommand(command)) {
|
|
793
832
|
if (flags.agent) {
|
|
794
833
|
if (positional.length > 1) {
|
|
795
|
-
throw new FlowError("EMPTY_PARAM",
|
|
834
|
+
throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument when --agent is used`);
|
|
796
835
|
}
|
|
797
836
|
}
|
|
798
837
|
else {
|
|
799
838
|
const [name] = positional;
|
|
800
839
|
if (!name) {
|
|
801
|
-
throw new FlowError("EMPTY_PARAM",
|
|
840
|
+
throw new FlowError("EMPTY_PARAM", `${command} requires an agent name; use \`keiyaku ${command} NAME [prompt|-]\``);
|
|
802
841
|
}
|
|
803
842
|
const parsedName = agentNameSchema.safeParse(name);
|
|
804
843
|
if (!parsedName.success) {
|
|
805
|
-
throw new FlowError("EMPTY_PARAM",
|
|
844
|
+
throw new FlowError("EMPTY_PARAM", `${command} requires a valid agent name before the optional prompt`);
|
|
806
845
|
}
|
|
807
846
|
flags.agent = parsedName.data;
|
|
808
847
|
positional.shift();
|
|
809
848
|
if (positional.length > 1) {
|
|
810
|
-
throw new FlowError("EMPTY_PARAM",
|
|
849
|
+
throw new FlowError("EMPTY_PARAM", `${command} accepts at most one prompt argument after the agent name`);
|
|
811
850
|
}
|
|
812
851
|
}
|
|
813
852
|
}
|
|
@@ -900,7 +939,7 @@ async function runResponse(command, flags, stdin, positional) {
|
|
|
900
939
|
return await getCliCommandSpec(command).run(stdin, flags, signal, positional);
|
|
901
940
|
}
|
|
902
941
|
function positionalPrompt(command, positional) {
|
|
903
|
-
if (command
|
|
942
|
+
if (isSummonCommand(command))
|
|
904
943
|
return positional[0];
|
|
905
944
|
if (command === "resume")
|
|
906
945
|
return positional[1];
|
|
@@ -1059,12 +1098,13 @@ export function renderCliHelp(version) {
|
|
|
1059
1098
|
" 工作流细节看 guide,命令格式看 --help",
|
|
1060
1099
|
"",
|
|
1061
1100
|
"Flow:",
|
|
1062
|
-
" 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]",
|
|
1063
1102
|
"",
|
|
1064
1103
|
"Commands:",
|
|
1065
1104
|
" bind 和Keiyaku签contract,创建keiyaku branch和KEIYAKU.md。内容确定了你就是contract的dog! 跪下!",
|
|
1066
1105
|
" round open 宣言这个round要干嘛。什么? 还没想好? 那签contract的时候在干嘛??",
|
|
1067
1106
|
" summon 召唤一只Zako来帮你干活。用完就扔 GWAHAHAHA!!",
|
|
1107
|
+
" ask summon的短alias。日常想少打几个字就用这个",
|
|
1068
1108
|
" resume 从response artifact恢复之前的Zako session",
|
|
1069
1109
|
" zako list|ls 看现在能召唤哪些Zako。别连手牌都不知道就开打",
|
|
1070
1110
|
" zako show 看某只Zako的provider/executable/settings。召唤前先验货",
|
|
@@ -1079,7 +1119,7 @@ export function renderCliHelp(version) {
|
|
|
1079
1119
|
"",
|
|
1080
1120
|
"Input:",
|
|
1081
1121
|
" bind, round open, round close, amend, petition claim 从stdin吃Markdown",
|
|
1082
|
-
" summon, resume 吃prompt参数;prompt是 - 或省略时从stdin读",
|
|
1122
|
+
" summon, ask, resume 吃prompt参数;prompt是 - 或省略时从stdin读",
|
|
1083
1123
|
" petition forfeit, zako list|ls, zako show, harness install, guide, status, dump-env 不吃stdin",
|
|
1084
1124
|
" 别再来问了。要精确Markdown sections就自己看 `keiyaku <command> --help`",
|
|
1085
1125
|
].join("\n");
|