@gajae-code/coding-agent 0.7.9 → 0.7.11
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/CHANGELOG.md +47 -0
- package/README.md +2 -0
- package/dist/types/config/keybindings.d.ts +11 -5
- package/dist/types/config/settings-schema.d.ts +10 -1
- package/dist/types/defaults/gjc-defaults.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +10 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +4 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +20 -6
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +8 -5
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +2 -0
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +24 -0
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +1 -1
- package/src/commands/launch.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/config/keybindings.ts +14 -5
- package/src/config/model-profiles.ts +16 -0
- package/src/config/settings-schema.ts +11 -1
- package/src/coordinator-mcp/server.ts +9 -3
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +26 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +16 -14
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/gjc-defaults.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/gjc-runtime/launch-tmux.ts +209 -26
- package/src/gjc-runtime/ralplan-runtime.ts +22 -6
- package/src/gjc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +8 -5
- package/src/gjc-runtime/state-writer.ts +5 -5
- package/src/gjc-runtime/ultragoal-guard.ts +139 -19
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -124
- package/src/gjc-runtime/workflow-manifest.generated.json +0 -8
- package/src/gjc-runtime/workflow-manifest.ts +0 -1
- package/src/hooks/skill-state.ts +33 -73
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/main.ts +2 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/components/custom-editor.ts +8 -0
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +6 -2
- package/src/modes/components/settings-selector.ts +47 -5
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/controllers/command-controller.ts +23 -0
- package/src/modes/controllers/extension-ui-controller.ts +18 -2
- package/src/modes/controllers/input-controller.ts +41 -9
- package/src/modes/interactive-mode.ts +16 -7
- package/src/modes/prompt-action-autocomplete.ts +23 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +53 -9
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +12 -1
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +68 -25
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/sdk.ts +6 -11
- package/src/session/agent-session.ts +60 -17
- package/src/slash-commands/builtin-registry.ts +30 -10
- package/src/slash-commands/types.ts +2 -0
- package/src/tools/ask.ts +136 -16
- package/src/tools/bash.ts +24 -8
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
|
@@ -11,7 +11,13 @@ import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
|
11
11
|
import { getNotificationConfig, isGloballyConfigured, tokenFingerprint } from "./config";
|
|
12
12
|
import { parseInThreadConfigCommand } from "./config-commands";
|
|
13
13
|
import { daemonPaths } from "./daemon-paths";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
buildCompactChoiceGrid,
|
|
16
|
+
code,
|
|
17
|
+
splitTelegramHtml,
|
|
18
|
+
TELEGRAM_MESSAGE_LIMIT,
|
|
19
|
+
TELEGRAM_PARSE_MODE,
|
|
20
|
+
} from "./html-format";
|
|
15
21
|
import type {
|
|
16
22
|
SessionCloseTarget,
|
|
17
23
|
SessionCreateTarget,
|
|
@@ -122,6 +128,21 @@ const QUEUED_REACTION = "👀";
|
|
|
122
128
|
const PENDING_TOPIC_FRAME_LIMIT = 20;
|
|
123
129
|
const CONSUMED_REACTION = "✅";
|
|
124
130
|
|
|
131
|
+
function splitTelegramPlainText(text: string, max = TELEGRAM_MESSAGE_LIMIT): string[] {
|
|
132
|
+
if (text.length <= max) return [text];
|
|
133
|
+
const chunks: string[] = [];
|
|
134
|
+
let out = "";
|
|
135
|
+
for (const ch of text) {
|
|
136
|
+
if (out.length + ch.length > max) {
|
|
137
|
+
chunks.push(out);
|
|
138
|
+
out = "";
|
|
139
|
+
}
|
|
140
|
+
out += ch;
|
|
141
|
+
}
|
|
142
|
+
if (out) chunks.push(out);
|
|
143
|
+
return chunks;
|
|
144
|
+
}
|
|
145
|
+
|
|
125
146
|
/**
|
|
126
147
|
* Whether `err` is a transient network failure worth retrying. Telegram API
|
|
127
148
|
* calls over HTTP/2 occasionally surface mid-stream `ECONNRESET` (and similar)
|
|
@@ -1053,14 +1074,29 @@ export class TelegramNotificationDaemon {
|
|
|
1053
1074
|
threadId: number | undefined,
|
|
1054
1075
|
): Promise<boolean> {
|
|
1055
1076
|
if (!isLifecycleCommandText(text)) return false;
|
|
1056
|
-
const reply = (body: string) =>
|
|
1057
|
-
|
|
1058
|
-
.
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1077
|
+
const reply = async (body: string): Promise<void> => {
|
|
1078
|
+
for (const text of splitTelegramPlainText(body)) {
|
|
1079
|
+
await this.botApi
|
|
1080
|
+
.call("sendMessage", {
|
|
1081
|
+
chat_id: this.opts.chatId,
|
|
1082
|
+
...(threadId !== undefined ? { message_thread_id: threadId } : {}),
|
|
1083
|
+
text,
|
|
1084
|
+
})
|
|
1085
|
+
.catch(() => undefined);
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
const replyHtml = async (body: string): Promise<void> => {
|
|
1089
|
+
for (const text of splitTelegramHtml(body)) {
|
|
1090
|
+
await this.botApi
|
|
1091
|
+
.call("sendMessage", {
|
|
1092
|
+
chat_id: this.opts.chatId,
|
|
1093
|
+
...(threadId !== undefined ? { message_thread_id: threadId } : {}),
|
|
1094
|
+
text,
|
|
1095
|
+
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1096
|
+
})
|
|
1097
|
+
.catch(() => undefined);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1064
1100
|
|
|
1065
1101
|
if (!this.lifecycleControlActive) {
|
|
1066
1102
|
await reply("Session lifecycle control is not available right now.");
|
|
@@ -1079,11 +1115,12 @@ export class TelegramNotificationDaemon {
|
|
|
1079
1115
|
const recent = listRecentSessions({
|
|
1080
1116
|
sessionsRoot: path.join(this.opts.settings.getAgentDir(), "sessions"),
|
|
1081
1117
|
limit: 10,
|
|
1118
|
+
includeInternal: false,
|
|
1082
1119
|
});
|
|
1083
|
-
const
|
|
1084
|
-
? recent.map(e =>
|
|
1120
|
+
const body = recent.length
|
|
1121
|
+
? recent.map(e => `• ${code(e.sessionId)}${e.path ? ` (${code(e.path)})` : ""}`).join("\n")
|
|
1085
1122
|
: "No recent sessions.";
|
|
1086
|
-
await
|
|
1123
|
+
await replyHtml(body);
|
|
1087
1124
|
return true;
|
|
1088
1125
|
}
|
|
1089
1126
|
|
|
@@ -1612,12 +1649,14 @@ export class TelegramNotificationDaemon {
|
|
|
1612
1649
|
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1613
1650
|
});
|
|
1614
1651
|
} else if (send.text) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1652
|
+
for (const text of splitTelegramHtml(send.text)) {
|
|
1653
|
+
await this.botApi.call("sendMessage", {
|
|
1654
|
+
chat_id: this.opts.chatId,
|
|
1655
|
+
...threadField,
|
|
1656
|
+
text,
|
|
1657
|
+
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1621
1660
|
}
|
|
1622
1661
|
} catch {
|
|
1623
1662
|
// Best-effort: a failed send must never stop the daemon.
|
|
@@ -1820,13 +1859,17 @@ export class TelegramNotificationDaemon {
|
|
|
1820
1859
|
const inline_keyboard = buildCompactChoiceGrid(options, (i: number) =>
|
|
1821
1860
|
this.aliasTable.put({ sessionId: session.sessionId, actionId: msg.id, answer: i }),
|
|
1822
1861
|
);
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1862
|
+
const chunks = splitTelegramHtml(rendered.text);
|
|
1863
|
+
let result: { result?: { message_id?: number } } = {};
|
|
1864
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1865
|
+
result = (await this.botApi.call("sendMessage", {
|
|
1866
|
+
chat_id: this.opts.chatId,
|
|
1867
|
+
...threadField,
|
|
1868
|
+
text: chunks[i]!,
|
|
1869
|
+
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1870
|
+
...(i === chunks.length - 1 && inline_keyboard.length ? { reply_markup: { inline_keyboard } } : {}),
|
|
1871
|
+
})) as { result?: { message_id?: number } };
|
|
1872
|
+
}
|
|
1830
1873
|
const messageId = result.result?.message_id;
|
|
1831
1874
|
if (messageId !== undefined)
|
|
1832
1875
|
this.messageRoutes.set(String(messageId), { sessionId: session.sessionId, actionId: msg.id });
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
buildCompactChoiceGrid,
|
|
21
21
|
escapeHtml,
|
|
22
22
|
numberedOptionList,
|
|
23
|
+
splitTelegramHtml,
|
|
23
24
|
TELEGRAM_PARSE_MODE,
|
|
24
|
-
truncateTelegramHtml,
|
|
25
25
|
} from "./html-format";
|
|
26
26
|
import { renderThreadedFrame } from "./threaded-render";
|
|
27
27
|
|
|
@@ -37,6 +37,8 @@ export interface RenderedMessage {
|
|
|
37
37
|
inline_keyboard?: InlineButton[][];
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
type TelegramSend = (method: string, body: unknown) => Promise<Response>;
|
|
41
|
+
|
|
40
42
|
/** Encode `actionId` + option `index` into Telegram callback_data (<=64 bytes). */
|
|
41
43
|
export function encodeCallbackData(actionId: string, index: number): string {
|
|
42
44
|
return `r:${index}:${actionId}`.slice(0, 64);
|
|
@@ -131,14 +133,32 @@ export function buildActionMessage(action: {
|
|
|
131
133
|
}): RenderedMessage {
|
|
132
134
|
if (action.kind === "idle") {
|
|
133
135
|
const text = action.summary ? `🟢 Agent idle\n${escapeHtml(action.summary)}` : "🟢 Agent idle";
|
|
134
|
-
return { text
|
|
136
|
+
return { text };
|
|
135
137
|
}
|
|
136
138
|
const text = `❓ ${bold(action.question ?? "Question")}`;
|
|
137
139
|
const options = action.options ?? [];
|
|
138
|
-
if (options.length === 0) return { text:
|
|
140
|
+
if (options.length === 0) return { text: `${text}\n\n(reply with text)` };
|
|
139
141
|
const body = `${text}\n\n${numberedOptionList(options)}`;
|
|
140
142
|
const inline_keyboard = buildCompactChoiceGrid(options, i => encodeCallbackData(action.id, i));
|
|
141
|
-
return { text:
|
|
143
|
+
return { text: body, inline_keyboard };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Send Telegram HTML text chunks sequentially so long messages preserve order. */
|
|
147
|
+
export async function sendTelegramHtmlChunks(
|
|
148
|
+
send: TelegramSend,
|
|
149
|
+
chatId: string,
|
|
150
|
+
text: string,
|
|
151
|
+
inlineKeyboard?: InlineButton[][],
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
const chunks = splitTelegramHtml(text);
|
|
154
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
155
|
+
await send("sendMessage", {
|
|
156
|
+
chat_id: chatId,
|
|
157
|
+
text: chunks[i]!,
|
|
158
|
+
parse_mode: TELEGRAM_PARSE_MODE,
|
|
159
|
+
...(i === chunks.length - 1 && inlineKeyboard ? { reply_markup: { inline_keyboard: inlineKeyboard } } : {}),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
/** A protocol `reply` frame the client should send to the server. */
|
|
@@ -285,15 +305,15 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
285
305
|
const ws = new WebSocket(`${url}/?token=${encodeURIComponent(token)}`);
|
|
286
306
|
let latestPendingAskId: string | undefined;
|
|
287
307
|
|
|
288
|
-
const send = (method
|
|
308
|
+
const send: TelegramSend = (method, body) =>
|
|
289
309
|
fetchImpl(`${api}/${method}`, {
|
|
290
310
|
method: "POST",
|
|
291
311
|
headers: { "content-type": "application/json" },
|
|
292
312
|
body: JSON.stringify(body),
|
|
293
313
|
});
|
|
294
314
|
|
|
295
|
-
|
|
296
|
-
const msg = JSON.parse(
|
|
315
|
+
const handleServerMessage = async (data: string): Promise<void> => {
|
|
316
|
+
const msg = JSON.parse(data) as {
|
|
297
317
|
type: string;
|
|
298
318
|
kind?: "ask" | "idle";
|
|
299
319
|
id?: string;
|
|
@@ -311,12 +331,7 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
311
331
|
options: msg.options,
|
|
312
332
|
summary: msg.summary,
|
|
313
333
|
});
|
|
314
|
-
|
|
315
|
-
chat_id: opts.chatId,
|
|
316
|
-
text: rendered.text,
|
|
317
|
-
parse_mode: TELEGRAM_PARSE_MODE,
|
|
318
|
-
...(rendered.inline_keyboard ? { reply_markup: { inline_keyboard: rendered.inline_keyboard } } : {}),
|
|
319
|
-
});
|
|
334
|
+
await sendTelegramHtmlChunks(send, opts.chatId, rendered.text, rendered.inline_keyboard);
|
|
320
335
|
} else if (msg.type === "action_resolved" && msg.id === latestPendingAskId) {
|
|
321
336
|
latestPendingAskId = undefined;
|
|
322
337
|
} else {
|
|
@@ -325,9 +340,16 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
325
340
|
// session's forum topic; this reference shows the minimal handling.
|
|
326
341
|
const threaded = renderThreadedFrame(msg as never);
|
|
327
342
|
if (threaded?.text) {
|
|
328
|
-
|
|
343
|
+
await sendTelegramHtmlChunks(send, opts.chatId, threaded.text);
|
|
329
344
|
}
|
|
330
345
|
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
let messageQueue = Promise.resolve();
|
|
349
|
+
ws.addEventListener("message", ev => {
|
|
350
|
+
const data = String(ev.data);
|
|
351
|
+
messageQueue = messageQueue.catch(() => undefined).then(() => handleServerMessage(data));
|
|
352
|
+
void messageQueue.catch(() => undefined);
|
|
331
353
|
});
|
|
332
354
|
|
|
333
355
|
// Telegram long-poll loop.
|
|
@@ -49,6 +49,7 @@ interface ThreadedFrame {
|
|
|
49
49
|
tokenUsage?: unknown;
|
|
50
50
|
model?: unknown;
|
|
51
51
|
diff?: unknown;
|
|
52
|
+
cwd?: unknown;
|
|
52
53
|
// turn_stream
|
|
53
54
|
phase?: unknown;
|
|
54
55
|
text?: unknown;
|
|
@@ -89,18 +90,25 @@ export function formatIdentityHeader(frame: {
|
|
|
89
90
|
/** Format a streamed context update into a compact block (omitting empty fields). */
|
|
90
91
|
export function formatContextUpdate(frame: ThreadedFrame): string | undefined {
|
|
91
92
|
const lines: string[] = [];
|
|
93
|
+
const session = str(frame.sessionId);
|
|
94
|
+
const cwd = str(frame.cwd);
|
|
92
95
|
const last = str(frame.lastMessage);
|
|
93
|
-
if (last) lines.push(escapeHtml(truncate(last, 600)));
|
|
94
96
|
const task = str(frame.task);
|
|
95
|
-
if (task) lines.push(`${italic("task:")} ${escapeHtml(task)}`);
|
|
96
97
|
const goal = str(frame.goal);
|
|
97
|
-
if (goal) lines.push(`${italic("goal:")} ${escapeHtml(goal)}`);
|
|
98
98
|
const usage = str(frame.tokenUsage);
|
|
99
99
|
const model = str(frame.model);
|
|
100
|
-
if (usage || model) lines.push(`ctx: ${code([usage, model].filter(Boolean).join(" · "))}`);
|
|
101
100
|
const diff = str(frame.diff);
|
|
101
|
+
if (!cwd && !last && !task && !goal && !usage && !model && !diff) return undefined;
|
|
102
|
+
const meta = [session ? `session: ${code(session)}` : undefined, cwd ? `cwd: ${code(cwd)}` : undefined].filter(
|
|
103
|
+
Boolean,
|
|
104
|
+
);
|
|
105
|
+
if (meta.length > 0) lines.push(meta.join(" · "));
|
|
106
|
+
if (last) lines.push(escapeHtml(truncate(last, 600)));
|
|
107
|
+
if (task) lines.push(`${italic("task:")} ${escapeHtml(task)}`);
|
|
108
|
+
if (goal) lines.push(`${italic("goal:")} ${escapeHtml(goal)}`);
|
|
109
|
+
if (usage || model) lines.push(`ctx: ${code([usage, model].filter(Boolean).join(" · "))}`);
|
|
102
110
|
if (diff) lines.push(`diff:\n${pre(truncate(diff, 1200))}`);
|
|
103
|
-
return lines.
|
|
111
|
+
return lines.join("\n");
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
/**
|
|
@@ -125,7 +133,7 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
|
|
|
125
133
|
case "turn_stream": {
|
|
126
134
|
const raw = str(frame.text);
|
|
127
135
|
if (!raw) return undefined;
|
|
128
|
-
const text =
|
|
136
|
+
const text = markdownToTelegramHtml(raw);
|
|
129
137
|
const finalized = frame.phase === "finalized";
|
|
130
138
|
return {
|
|
131
139
|
method: "sendMessage",
|
|
@@ -25,7 +25,7 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
25
25
|
|
|
26
26
|
<constraints>
|
|
27
27
|
- Read-only: never write, edit, format, commit, push, or mutate files.
|
|
28
|
-
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown
|
|
28
|
+
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
29
29
|
- Never approve code or plans you have not grounded in inspected files.
|
|
30
30
|
- Never give generic advice detached from this codebase.
|
|
31
31
|
- Never approve CRITICAL or HIGH severity issues.
|
|
@@ -63,6 +63,9 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
|
|
|
63
63
|
## Summary
|
|
64
64
|
2-3 sentences with result and main recommendation.
|
|
65
65
|
|
|
66
|
+
## Claims
|
|
67
|
+
Evidence-backed claims being reviewed or introduced.
|
|
68
|
+
|
|
66
69
|
## Analysis
|
|
67
70
|
Evidence-backed findings.
|
|
68
71
|
|
|
@@ -81,12 +84,12 @@ Prioritized concrete actions.
|
|
|
81
84
|
## Code Review Recommendation
|
|
82
85
|
`APPROVE` / `COMMENT` / `REQUEST CHANGES`
|
|
83
86
|
|
|
84
|
-
##
|
|
87
|
+
## Tradeoffs
|
|
85
88
|
Table or bullets comparing viable options when relevant.
|
|
86
89
|
|
|
87
|
-
Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown
|
|
90
|
+
Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
|
|
88
91
|
|
|
89
|
-
gjc ralplan --write --stage architect --stage_n <N> --artifact
|
|
92
|
+
gjc ralplan --write --stage architect --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
90
93
|
|
|
91
94
|
Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (Architectural Status + Code Review Recommendation). Never paste the full review body back into your response — the caller reads the persisted artifact when it needs the full text.
|
|
92
95
|
</output_contract>
|
|
@@ -17,7 +17,7 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
17
17
|
|
|
18
18
|
<constraints>
|
|
19
19
|
- Read-only: do not write, edit, format, commit, push, or mutate files.
|
|
20
|
-
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown
|
|
20
|
+
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
21
21
|
- A lone file path is valid input; read and evaluate it.
|
|
22
22
|
- Reject YAML-only plans as invalid plan format when a human-readable plan is required.
|
|
23
23
|
- Do not invent problems; report no issues found when the plan passes.
|
|
@@ -42,11 +42,19 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
42
42
|
</success_criteria>
|
|
43
43
|
|
|
44
44
|
<output_contract>
|
|
45
|
+
## Verdict
|
|
45
46
|
**[OKAY / ITERATE / REJECT]**
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
## Claim Checks
|
|
49
|
+
Concise evidence-backed explanation of verified claims.
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
## Missing Evidence
|
|
52
|
+
Definitely missing or unverified evidence, or `None`.
|
|
53
|
+
|
|
54
|
+
## Approval Boundary
|
|
55
|
+
What execution may proceed with, and what remains outside approval.
|
|
56
|
+
|
|
57
|
+
## Summary
|
|
50
58
|
- Clarity
|
|
51
59
|
- Verifiability
|
|
52
60
|
- Completeness
|
|
@@ -55,11 +63,12 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
55
63
|
- Alternatives Depth
|
|
56
64
|
- Risk/Verification Rigor
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
## Required Changes
|
|
67
|
+
If not OKAY, list concrete required fixes; otherwise write `None`.
|
|
59
68
|
|
|
60
|
-
Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown
|
|
69
|
+
Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
|
|
61
70
|
|
|
62
|
-
gjc ralplan --write --stage critic --stage_n <N> --artifact
|
|
71
|
+
gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
63
72
|
|
|
64
73
|
Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (OKAY / ITERATE / REJECT). Never paste the full evaluation body back into your response — the caller reads the persisted artifact when it needs the full text.
|
|
65
74
|
</output_contract>
|
|
@@ -17,7 +17,7 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
|
|
|
17
17
|
|
|
18
18
|
<constraints>
|
|
19
19
|
- Read-only: never write, edit, format, commit, push, or mutate files.
|
|
20
|
-
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown
|
|
20
|
+
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
21
21
|
- Persist durable plans only through `gjc ralplan --write`. Never write plan files to `/tmp`, the repository, or any other path, and never rely on a file the caller must read back. The CLI is your only persistence channel.
|
|
22
22
|
- Inspect the repository before asking about code facts.
|
|
23
23
|
- Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences that repository inspection cannot resolve.
|
|
@@ -45,16 +45,21 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
|
|
|
45
45
|
<output_contract>
|
|
46
46
|
Build the full plan as a single markdown document containing:
|
|
47
47
|
- Summary
|
|
48
|
+
- Intent Diff
|
|
49
|
+
- Decision Drivers
|
|
50
|
+
- Options
|
|
48
51
|
- In scope / out of scope
|
|
49
52
|
- File-level changes
|
|
50
53
|
- Sequencing and dependencies
|
|
51
54
|
- Acceptance criteria
|
|
52
55
|
- Verification
|
|
56
|
+
- Escalation/Risk Gate
|
|
57
|
+
- Verification Plan
|
|
53
58
|
- Risks and mitigations
|
|
54
59
|
|
|
55
|
-
Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan
|
|
60
|
+
Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
|
|
56
61
|
|
|
57
|
-
gjc ralplan --write --stage planner --stage_n <N> --artifact
|
|
62
|
+
gjc ralplan --write --stage planner --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
58
63
|
|
|
59
64
|
Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus a compact plan summary (<=10 lines). Never paste the full plan body back into your response — the caller reads the persisted artifact when it needs the full text.
|
|
60
65
|
</output_contract>
|
|
@@ -60,6 +60,9 @@ Use for read-only plan critique. It approves only when execution can proceed wit
|
|
|
60
60
|
|
|
61
61
|
<routing>
|
|
62
62
|
- Clear, low-risk implementation request → implement directly with focused verification.
|
|
63
|
+
- Do not invoke `deep-interview`, `ralplan`, `ultragoal`, `team`, or role agents for simple clear implementation requests; direct tools and the default launch path are enough.
|
|
64
|
+
- When a task is clear, bounded, and low-risk, the default action is to make the smallest correct change and verify it, not to interview, plan, open a durable ledger, or delegate.
|
|
65
|
+
- Small verification needs do not make a task a planning workflow. Escalate only for real ambiguity, non-trivial architecture/sequence risk, durable multi-goal tracking, or useful coordinated workers.
|
|
63
66
|
- Vague requirements → use `deep-interview` before planning or execution.
|
|
64
67
|
- Clear requirements but non-trivial architecture/sequence risk → use `ralplan` and stop at pending approval.
|
|
65
68
|
- Durable goal ledger needed → use `ultragoal`; if no approved plan exists, run `ralplan` first.
|
|
@@ -238,9 +241,10 @@ For image understanding, call `{{toolRefs.read}}` on the image path; the image i
|
|
|
238
241
|
</before-editing>
|
|
239
242
|
|
|
240
243
|
<decomposition>
|
|
241
|
-
- Use todo tracking for tasks with three or more distinct steps.
|
|
244
|
+
- Use todo tracking for tasks with three or more distinct steps; skip it for one-step or obvious two-step fixes where the next action is already clear.
|
|
242
245
|
- Mark completed tasks immediately and continue to the next task without yielding.
|
|
243
246
|
- Delegate rather than silently shrinking scope. Prefer `executor` for bounded implementation slices, `planner` for sequencing, `architect` for architecture/code-review lanes, and `critic` for plan critique.
|
|
247
|
+
- Do not delegate for single-line typos, obvious syntax errors, single-file known-location fixes, or direct answers.
|
|
244
248
|
</decomposition>
|
|
245
249
|
|
|
246
250
|
<verification>
|
|
@@ -33,7 +33,7 @@ This session's bash tool is restricted. It only accepts commands beginning with:
|
|
|
33
33
|
{{#each restrictedAllowedPrefixes}}
|
|
34
34
|
- `{{this}}`
|
|
35
35
|
{{/each}}
|
|
36
|
-
Use it only for sanctioned GJC workflow CLI persistence or state read/write/contract operations; per-command env
|
|
36
|
+
Use it only for sanctioned GJC workflow CLI persistence or state read/write/contract operations; the only per-command env override allowed is `GJC_RALPLAN_ARTIFACT` when paired with `gjc ralplan --write ... --artifact-env GJC_RALPLAN_ARTIFACT`, and all other shell command shapes are blocked before execution.
|
|
37
37
|
{{/when}}
|
|
38
38
|
</restricted-bash-mode>
|
|
39
39
|
{{/if}}
|
package/src/sdk.ts
CHANGED
|
@@ -119,6 +119,7 @@ import { AgentOutputManager } from "./task/output-manager";
|
|
|
119
119
|
import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
|
|
120
120
|
import { collectDiscoverableTools, type DiscoverableTool } from "./tool-discovery/tool-index";
|
|
121
121
|
import {
|
|
122
|
+
applyConfiguredSearchTimeout,
|
|
122
123
|
BashTool,
|
|
123
124
|
BUILTIN_TOOLS,
|
|
124
125
|
computeEssentialBuiltinNames,
|
|
@@ -127,10 +128,10 @@ import {
|
|
|
127
128
|
EditTool,
|
|
128
129
|
EvalTool,
|
|
129
130
|
FindTool,
|
|
131
|
+
getConfiguredSearchProviderPreference,
|
|
130
132
|
getSearchTools,
|
|
131
133
|
HIDDEN_TOOLS,
|
|
132
134
|
isConfigurableSearchProviderId,
|
|
133
|
-
isSearchProviderPreference,
|
|
134
135
|
type LspStartupServerInfo,
|
|
135
136
|
loadSshTool,
|
|
136
137
|
ReadTool,
|
|
@@ -140,7 +141,6 @@ import {
|
|
|
140
141
|
setPreferredImageProvider,
|
|
141
142
|
setPreferredSearchProvider,
|
|
142
143
|
setSearchFallbackProviders,
|
|
143
|
-
setSearchHardTimeoutMs,
|
|
144
144
|
type Tool,
|
|
145
145
|
type ToolSession,
|
|
146
146
|
WebSearchTool,
|
|
@@ -255,7 +255,7 @@ export interface CreateAgentSessionOptions {
|
|
|
255
255
|
thinkingLevel?: ThinkingLevel;
|
|
256
256
|
/** Runtime substitution metadata for the initial model_change session event. */
|
|
257
257
|
modelSubstitution?: { requestedModel: Model; reason: string };
|
|
258
|
-
/** Models available for cycling (
|
|
258
|
+
/** Models available for cycling (Alt+N in interactive mode) */
|
|
259
259
|
scopedModels?: ScopedModelSelection[];
|
|
260
260
|
|
|
261
261
|
/** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
|
|
@@ -932,20 +932,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
932
932
|
slashCommandsPromise.catch(() => {});
|
|
933
933
|
|
|
934
934
|
// Initialize provider preferences from settings
|
|
935
|
-
const webSearchProvider = settings
|
|
936
|
-
|
|
937
|
-
setPreferredSearchProvider(webSearchProvider);
|
|
938
|
-
}
|
|
935
|
+
const webSearchProvider = getConfiguredSearchProviderPreference(settings);
|
|
936
|
+
setPreferredSearchProvider(webSearchProvider);
|
|
939
937
|
const webSearchFallback = settings.get("web_search.fallback");
|
|
940
938
|
if (Array.isArray(webSearchFallback)) {
|
|
941
939
|
setSearchFallbackProviders(
|
|
942
940
|
webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
|
|
943
941
|
);
|
|
944
942
|
}
|
|
945
|
-
|
|
946
|
-
if (typeof webSearchTimeout === "number" && Number.isFinite(webSearchTimeout) && webSearchTimeout > 0) {
|
|
947
|
-
setSearchHardTimeoutMs(webSearchTimeout * 1000);
|
|
948
|
-
}
|
|
943
|
+
applyConfiguredSearchTimeout(settings);
|
|
949
944
|
|
|
950
945
|
const imageProvider = settings.get("providers.image");
|
|
951
946
|
if (
|
|
@@ -353,7 +353,7 @@ export interface AgentSessionConfig {
|
|
|
353
353
|
agent: Agent;
|
|
354
354
|
sessionManager: SessionManager;
|
|
355
355
|
settings: Settings;
|
|
356
|
-
/** Models to cycle through with
|
|
356
|
+
/** Models to cycle through with Alt+N (from --models flag) */
|
|
357
357
|
scopedModels?: ScopedModelSelection[];
|
|
358
358
|
/** Initial session thinking selector. */
|
|
359
359
|
thinkingLevel?: ThinkingLevel;
|
|
@@ -901,8 +901,9 @@ function extractPermissionLocations(
|
|
|
901
901
|
* `tag` is set only by `enqueueCustomMessageDisplay` (used for skill-prompt
|
|
902
902
|
* custom messages queued during streaming) and is matched by the custom-role
|
|
903
903
|
* `message_start` dequeue branch; user-message pushes leave it undefined and
|
|
904
|
-
* rely on the existing text-equality match.
|
|
905
|
-
|
|
904
|
+
* rely on the existing text-equality match. `sequence` preserves cross-queue
|
|
905
|
+
* insertion order for one-at-a-time dequeue/edit UX. */
|
|
906
|
+
type QueuedDisplayEntry = { text: string; tag?: string; sequence: number };
|
|
906
907
|
|
|
907
908
|
/** A custom message contributed at the before-agent-start point. */
|
|
908
909
|
export type BeforeAgentStartInternalMessage = Pick<
|
|
@@ -956,6 +957,7 @@ export class AgentSession {
|
|
|
956
957
|
/** Tracks pending follow-up messages for UI display. Removed when delivered.
|
|
957
958
|
* See `#steeringMessages` for entry shape. */
|
|
958
959
|
#followUpMessages: QueuedDisplayEntry[] = [];
|
|
960
|
+
#queuedDisplaySequence = 0;
|
|
959
961
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
960
962
|
#pendingNextTurnMessages: CustomMessage[] = [];
|
|
961
963
|
#scheduledHiddenNextTurnGeneration: number | undefined = undefined;
|
|
@@ -1625,6 +1627,14 @@ export class AgentSession {
|
|
|
1625
1627
|
this.#planCompactAbortPending = false;
|
|
1626
1628
|
}
|
|
1627
1629
|
|
|
1630
|
+
#createQueuedDisplayEntry(text: string, tag?: string): QueuedDisplayEntry {
|
|
1631
|
+
const entry: QueuedDisplayEntry = { text, sequence: ++this.#queuedDisplaySequence };
|
|
1632
|
+
if (tag !== undefined) {
|
|
1633
|
+
entry.tag = tag;
|
|
1634
|
+
}
|
|
1635
|
+
return entry;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1628
1638
|
/** Register a compact display string for a custom message that the caller is
|
|
1629
1639
|
* about to dispatch via `promptCustomMessage` / `sendCustomMessage`.
|
|
1630
1640
|
* Returns a stable tag the caller MUST embed in
|
|
@@ -1638,7 +1648,7 @@ export class AgentSession {
|
|
|
1638
1648
|
const tag = `gjc-cmd-${Date.now()}-${++this.#customDisplayTagCounter}`;
|
|
1639
1649
|
const displayText = text.trim();
|
|
1640
1650
|
if (!displayText) return tag;
|
|
1641
|
-
const entry
|
|
1651
|
+
const entry = this.#createQueuedDisplayEntry(displayText, tag);
|
|
1642
1652
|
if (mode === "steer") {
|
|
1643
1653
|
this.#steeringMessages.push(entry);
|
|
1644
1654
|
} else {
|
|
@@ -5285,7 +5295,7 @@ export class AgentSession {
|
|
|
5285
5295
|
*/
|
|
5286
5296
|
async #queueSteer(text: string, images?: ImageContent[]): Promise<void> {
|
|
5287
5297
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5288
|
-
this.#steeringMessages.push(
|
|
5298
|
+
this.#steeringMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5289
5299
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
5290
5300
|
if (images && images.length > 0) {
|
|
5291
5301
|
content.push(...images);
|
|
@@ -5316,7 +5326,7 @@ export class AgentSession {
|
|
|
5316
5326
|
*/
|
|
5317
5327
|
async #queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {
|
|
5318
5328
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5319
|
-
this.#followUpMessages.push(
|
|
5329
|
+
this.#followUpMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5320
5330
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
5321
5331
|
if (images && images.length > 0) {
|
|
5322
5332
|
content.push(...images);
|
|
@@ -5686,24 +5696,27 @@ export class AgentSession {
|
|
|
5686
5696
|
}
|
|
5687
5697
|
|
|
5688
5698
|
/**
|
|
5689
|
-
* Pop the
|
|
5699
|
+
* Pop the newest queued message across steering and follow-up queues.
|
|
5690
5700
|
* Used by dequeue keybinding to restore messages to editor one at a time.
|
|
5691
5701
|
* Returns the popped entry's `.text`; the tag (if any) dies with the
|
|
5692
5702
|
* record — no orphan state can outlive the queue entry.
|
|
5693
5703
|
*/
|
|
5694
5704
|
popLastQueuedMessage(): string | undefined {
|
|
5695
|
-
|
|
5696
|
-
|
|
5705
|
+
const steeringEntry = this.#steeringMessages.at(-1);
|
|
5706
|
+
const followUpEntry = this.#followUpMessages.at(-1);
|
|
5707
|
+
|
|
5708
|
+
if (steeringEntry && (!followUpEntry || steeringEntry.sequence > followUpEntry.sequence)) {
|
|
5697
5709
|
const entry = this.#steeringMessages.pop();
|
|
5698
5710
|
this.agent.popLastSteer();
|
|
5699
5711
|
return entry?.text;
|
|
5700
5712
|
}
|
|
5701
|
-
|
|
5702
|
-
if (
|
|
5713
|
+
|
|
5714
|
+
if (followUpEntry) {
|
|
5703
5715
|
const entry = this.#followUpMessages.pop();
|
|
5704
5716
|
this.agent.popLastFollowUp();
|
|
5705
5717
|
return entry?.text;
|
|
5706
5718
|
}
|
|
5719
|
+
|
|
5707
5720
|
return undefined;
|
|
5708
5721
|
}
|
|
5709
5722
|
|
|
@@ -8932,22 +8945,52 @@ export class AgentSession {
|
|
|
8932
8945
|
setAutoRetryEnabled(enabled: boolean): void {
|
|
8933
8946
|
this.settings.set("retry.enabled", enabled);
|
|
8934
8947
|
}
|
|
8948
|
+
#isInterruptedRetryTail(message: AgentMessage | undefined): boolean {
|
|
8949
|
+
if (!message) return false;
|
|
8950
|
+
return (
|
|
8951
|
+
message.role === "user" ||
|
|
8952
|
+
message.role === "developer" ||
|
|
8953
|
+
message.role === "toolResult" ||
|
|
8954
|
+
message.role === "fileMention" ||
|
|
8955
|
+
message.role === "custom" ||
|
|
8956
|
+
message.role === "hookMessage"
|
|
8957
|
+
);
|
|
8958
|
+
}
|
|
8959
|
+
|
|
8960
|
+
#isUnresolvedToolUseAssistant(message: AssistantMessage): boolean {
|
|
8961
|
+
return message.stopReason === "toolUse" && message.content.some(content => content.type === "toolCall");
|
|
8962
|
+
}
|
|
8963
|
+
|
|
8935
8964
|
/**
|
|
8936
|
-
* Manually retry the last failed assistant turn
|
|
8937
|
-
*
|
|
8938
|
-
*
|
|
8965
|
+
* Manually retry the last failed assistant turn, or resume an interrupted tail
|
|
8966
|
+
* left by a non-graceful process exit after the user/custom/tool-result message
|
|
8967
|
+
* was persisted but before the agent emitted a terminal assistant response.
|
|
8968
|
+
* Removes failed/aborted/unresolved tool-use assistant tails before
|
|
8969
|
+
* re-attempting with a fresh retry budget.
|
|
8970
|
+
* @returns true if retry/resume was initiated, false if no retryable tail exists or agent is busy
|
|
8939
8971
|
*/
|
|
8940
8972
|
async retry(): Promise<boolean> {
|
|
8941
8973
|
if (this.isStreaming || this.isCompacting || this.isRetrying) return false;
|
|
8942
8974
|
|
|
8943
8975
|
const messages = this.agent.state.messages;
|
|
8944
8976
|
const lastMsg = messages[messages.length - 1];
|
|
8945
|
-
if (lastMsg
|
|
8977
|
+
if (!lastMsg) return false;
|
|
8978
|
+
|
|
8979
|
+
if (lastMsg.role !== "assistant") {
|
|
8980
|
+
if (!this.#isInterruptedRetryTail(lastMsg)) return false;
|
|
8981
|
+
this.#retryAttempt = 0;
|
|
8982
|
+
this.#scheduleAgentContinue({ delayMs: 1 });
|
|
8983
|
+
return true;
|
|
8984
|
+
}
|
|
8946
8985
|
|
|
8947
8986
|
const assistantMsg = lastMsg as AssistantMessage;
|
|
8948
|
-
|
|
8987
|
+
const shouldDropAssistant =
|
|
8988
|
+
assistantMsg.stopReason === "error" ||
|
|
8989
|
+
assistantMsg.stopReason === "aborted" ||
|
|
8990
|
+
this.#isUnresolvedToolUseAssistant(assistantMsg);
|
|
8991
|
+
if (!shouldDropAssistant) return false;
|
|
8949
8992
|
|
|
8950
|
-
// Remove the failed/aborted assistant message
|
|
8993
|
+
// Remove the failed/aborted/incomplete assistant message before re-attempting.
|
|
8951
8994
|
this.agent.replaceMessages(messages.slice(0, -1));
|
|
8952
8995
|
|
|
8953
8996
|
// Reset retry budget for a fresh attempt
|