@gajae-code/coding-agent 0.7.8 → 0.7.10
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 +40 -0
- package/README.md +8 -0
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +19 -2
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +19 -3
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/modes/components/hook-input.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line.d.ts +3 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/notifications/daemon-paths.d.ts +9 -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-cli.d.ts +8 -2
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +1 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +26 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/cmux-workspace.d.ts +23 -0
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +5 -0
- package/dist/types/web/search/render.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/notify-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +3 -2
- package/src/cli.ts +1 -0
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profile-activation.ts +92 -11
- package/src/config/model-profiles.ts +15 -2
- package/src/config/model-registry.ts +81 -5
- package/src/config/settings-schema.ts +14 -3
- package/src/coordinator-mcp/server.ts +4 -2
- package/src/deep-interview/render-middleware.ts +1 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +40 -8
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -112
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +366 -48
- package/src/gjc-runtime/team-runtime.ts +69 -4
- package/src/gjc-runtime/ultragoal-guard.ts +150 -24
- 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 +8 -7
- package/src/modes/components/custom-model-preset-wizard.ts +2 -4
- package/src/modes/components/hook-editor.ts +7 -4
- package/src/modes/components/hook-input.ts +4 -0
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +84 -13
- package/src/modes/components/settings-selector.ts +83 -24
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +10 -2
- package/src/modes/components/status-line.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +130 -7
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +64 -13
- package/src/modes/types.ts +6 -1
- package/src/notifications/daemon-paths.ts +22 -0
- 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-cli.ts +13 -4
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +69 -45
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/system/system-prompt.md +10 -1
- package/src/sdk.ts +13 -6
- package/src/session/agent-session.ts +10 -1
- package/src/tools/ask.ts +147 -17
- package/src/tools/eval.ts +2 -2
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/cmux-workspace.ts +106 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/search/index.ts +22 -10
- package/src/web/search/provider.ts +15 -0
- package/src/web/search/render.ts +3 -0
|
@@ -97,6 +97,14 @@ export interface TelegramDaemonControlDeps {
|
|
|
97
97
|
sendSignal?: (pid: number, signal: NodeJS.Signals) => void;
|
|
98
98
|
spawn?: TelegramDaemonDeps["spawn"];
|
|
99
99
|
execPath?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Stable process id encoded into freshly-spawned daemon owner ids.
|
|
102
|
+
*
|
|
103
|
+
* The daemon-internal entrypoint rejects numeric owner ids whose process is
|
|
104
|
+
* already gone. `gjc daemon reload` is a short-lived CLI process, so using its
|
|
105
|
+
* own pid can race the child startup and make the replacement exit immediately.
|
|
106
|
+
*/
|
|
107
|
+
ownerPid?: number;
|
|
100
108
|
randomId?: () => string;
|
|
101
109
|
sleep?: (ms: number) => Promise<void>;
|
|
102
110
|
waitStepMs?: number;
|
|
@@ -188,6 +196,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
188
196
|
fs: this.deps.fs,
|
|
189
197
|
now: this.deps.now,
|
|
190
198
|
pidAlive: this.deps.pidAlive,
|
|
199
|
+
pid: this.deps.ownerPid ?? process.ppid,
|
|
191
200
|
spawn: this.deps.spawn,
|
|
192
201
|
execPath: this.deps.execPath,
|
|
193
202
|
randomId: this.deps.randomId,
|
|
@@ -10,7 +10,14 @@ import type { DaemonRuntimeInfo } from "../daemon/control-types";
|
|
|
10
10
|
import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
11
11
|
import { getNotificationConfig, isGloballyConfigured, tokenFingerprint } from "./config";
|
|
12
12
|
import { parseInThreadConfigCommand } from "./config-commands";
|
|
13
|
-
import {
|
|
13
|
+
import { daemonPaths } from "./daemon-paths";
|
|
14
|
+
import {
|
|
15
|
+
buildCompactChoiceGrid,
|
|
16
|
+
pre,
|
|
17
|
+
splitTelegramHtml,
|
|
18
|
+
TELEGRAM_MESSAGE_LIMIT,
|
|
19
|
+
TELEGRAM_PARSE_MODE,
|
|
20
|
+
} from "./html-format";
|
|
14
21
|
import type {
|
|
15
22
|
SessionCloseTarget,
|
|
16
23
|
SessionCreateTarget,
|
|
@@ -62,16 +69,6 @@ export interface DaemonState {
|
|
|
62
69
|
version: 1;
|
|
63
70
|
stoppedAt?: number;
|
|
64
71
|
}
|
|
65
|
-
|
|
66
|
-
export interface DaemonPaths {
|
|
67
|
-
dir: string;
|
|
68
|
-
lock: string;
|
|
69
|
-
state: string;
|
|
70
|
-
roots: string;
|
|
71
|
-
steal: string;
|
|
72
|
-
aliases: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
72
|
export interface TelegramDaemonFs {
|
|
76
73
|
mkdir(path: string, opts?: fs.MakeDirectoryOptions): Promise<void>;
|
|
77
74
|
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
@@ -131,6 +128,21 @@ const QUEUED_REACTION = "👀";
|
|
|
131
128
|
const PENDING_TOPIC_FRAME_LIMIT = 20;
|
|
132
129
|
const CONSUMED_REACTION = "✅";
|
|
133
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
|
+
|
|
134
146
|
/**
|
|
135
147
|
* Whether `err` is a transient network failure worth retrying. Telegram API
|
|
136
148
|
* calls over HTTP/2 occasionally surface mid-stream `ECONNRESET` (and similar)
|
|
@@ -184,17 +196,7 @@ async function fetchWithRetry(
|
|
|
184
196
|
throw lastErr;
|
|
185
197
|
}
|
|
186
198
|
|
|
187
|
-
export
|
|
188
|
-
const dir = path.join(agentDir, "notifications");
|
|
189
|
-
return {
|
|
190
|
-
dir,
|
|
191
|
-
lock: path.join(dir, "telegram-daemon.lock"),
|
|
192
|
-
state: path.join(dir, "telegram-daemon.state.json"),
|
|
193
|
-
roots: path.join(dir, "telegram-daemon.roots.json"),
|
|
194
|
-
steal: path.join(dir, "telegram-daemon.steal"),
|
|
195
|
-
aliases: path.join(dir, "telegram-callback-aliases.json"),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
199
|
+
export { type DaemonPaths, daemonPaths } from "./daemon-paths";
|
|
198
200
|
|
|
199
201
|
/**
|
|
200
202
|
* Attach session-lifecycle control (create/close/resume) to the running daemon.
|
|
@@ -1072,14 +1074,29 @@ export class TelegramNotificationDaemon {
|
|
|
1072
1074
|
threadId: number | undefined,
|
|
1073
1075
|
): Promise<boolean> {
|
|
1074
1076
|
if (!isLifecycleCommandText(text)) return false;
|
|
1075
|
-
const reply = (body: string) =>
|
|
1076
|
-
|
|
1077
|
-
.
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
+
};
|
|
1083
1100
|
|
|
1084
1101
|
if (!this.lifecycleControlActive) {
|
|
1085
1102
|
await reply("Session lifecycle control is not available right now.");
|
|
@@ -1098,11 +1115,12 @@ export class TelegramNotificationDaemon {
|
|
|
1098
1115
|
const recent = listRecentSessions({
|
|
1099
1116
|
sessionsRoot: path.join(this.opts.settings.getAgentDir(), "sessions"),
|
|
1100
1117
|
limit: 10,
|
|
1118
|
+
includeInternal: false,
|
|
1101
1119
|
});
|
|
1102
1120
|
const lines = recent.length
|
|
1103
|
-
? recent.map(e =>
|
|
1121
|
+
? recent.map(e => `• ${e.sessionId}${e.path ? ` (${e.path})` : ""}`).join("\n")
|
|
1104
1122
|
: "No recent sessions.";
|
|
1105
|
-
await
|
|
1123
|
+
await replyHtml(pre(lines));
|
|
1106
1124
|
return true;
|
|
1107
1125
|
}
|
|
1108
1126
|
|
|
@@ -1631,12 +1649,14 @@ export class TelegramNotificationDaemon {
|
|
|
1631
1649
|
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1632
1650
|
});
|
|
1633
1651
|
} else if (send.text) {
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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
|
+
}
|
|
1640
1660
|
}
|
|
1641
1661
|
} catch {
|
|
1642
1662
|
// Best-effort: a failed send must never stop the daemon.
|
|
@@ -1839,13 +1859,17 @@ export class TelegramNotificationDaemon {
|
|
|
1839
1859
|
const inline_keyboard = buildCompactChoiceGrid(options, (i: number) =>
|
|
1840
1860
|
this.aliasTable.put({ sessionId: session.sessionId, actionId: msg.id, answer: i }),
|
|
1841
1861
|
);
|
|
1842
|
-
const
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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
|
+
}
|
|
1849
1873
|
const messageId = result.result?.message_id;
|
|
1850
1874
|
if (messageId !== undefined)
|
|
1851
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",
|
|
@@ -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.
|
|
@@ -82,6 +85,11 @@ Use for read-only plan critique. It approves only when execution can proceed wit
|
|
|
82
85
|
- Do not load or inject user-home Anthropic model or provider instructions (`~/.anthropic-model`, `~/.openai-code`) into the model context.
|
|
83
86
|
- Public commands, paths, examples, and workflow names must use `gjc` and `.gjc`.
|
|
84
87
|
</runtime-state>
|
|
88
|
+
<self-awareness>
|
|
89
|
+
- When the user asks about GJC usage, how to use a GJC feature/command/workflow, or about the gajae-code system itself, do not answer from memory alone. First clone the canonical source repository into `/tmp` (e.g. `git clone --depth 1 https://github.com/Yeachan-Heo/gajae-code /tmp/gajae-code-<unique>`), then read and analyze the actual source there to ground your answer.
|
|
90
|
+
- Reuse an existing fresh clone under `/tmp` instead of re-cloning when one is already present in the session.
|
|
91
|
+
- Base usage and system answers on what the cloned source actually says; cite concrete files/paths from the clone rather than guessing.
|
|
92
|
+
</self-awareness>
|
|
85
93
|
</gjc-runtime>
|
|
86
94
|
|
|
87
95
|
<communication>
|
|
@@ -233,9 +241,10 @@ For image understanding, call `{{toolRefs.read}}` on the image path; the image i
|
|
|
233
241
|
</before-editing>
|
|
234
242
|
|
|
235
243
|
<decomposition>
|
|
236
|
-
- 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.
|
|
237
245
|
- Mark completed tasks immediately and continue to the next task without yielding.
|
|
238
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.
|
|
239
248
|
</decomposition>
|
|
240
249
|
|
|
241
250
|
<verification>
|
package/src/sdk.ts
CHANGED
|
@@ -127,10 +127,10 @@ import {
|
|
|
127
127
|
EditTool,
|
|
128
128
|
EvalTool,
|
|
129
129
|
FindTool,
|
|
130
|
+
getConfiguredSearchProviderPreference,
|
|
130
131
|
getSearchTools,
|
|
131
132
|
HIDDEN_TOOLS,
|
|
132
133
|
isConfigurableSearchProviderId,
|
|
133
|
-
isSearchProviderPreference,
|
|
134
134
|
type LspStartupServerInfo,
|
|
135
135
|
loadSshTool,
|
|
136
136
|
ReadTool,
|
|
@@ -932,10 +932,8 @@ 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(
|
|
@@ -2007,7 +2005,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2007
2005
|
.map(name => toolRegistry.get(name))
|
|
2008
2006
|
.filter((tool): tool is AgentTool => tool !== undefined)
|
|
2009
2007
|
// AgentSession tool wrapping is not installed until after Agent construction.
|
|
2010
|
-
.map(tool =>
|
|
2008
|
+
.map(tool =>
|
|
2009
|
+
guardToolForUltragoalAsk(
|
|
2010
|
+
tool,
|
|
2011
|
+
() => sessionManager.getCwd(),
|
|
2012
|
+
() => ({
|
|
2013
|
+
activeSkillState: session?.getActiveSkillState(),
|
|
2014
|
+
sessionId: sessionManager.getSessionId?.() ?? null,
|
|
2015
|
+
}),
|
|
2016
|
+
),
|
|
2017
|
+
);
|
|
2011
2018
|
|
|
2012
2019
|
const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
|
|
2013
2020
|
const preferOpenAICodexWebsockets =
|
|
@@ -3920,7 +3920,16 @@ export class AgentSession {
|
|
|
3920
3920
|
|
|
3921
3921
|
#prepareToolForExecution<T extends AgentTool>(tool: T): T {
|
|
3922
3922
|
return this.#wrapToolForDeepInterviewMutationGuard(
|
|
3923
|
-
this.#wrapToolForAcpPermission(
|
|
3923
|
+
this.#wrapToolForAcpPermission(
|
|
3924
|
+
guardToolForUltragoalAsk(
|
|
3925
|
+
tool,
|
|
3926
|
+
() => this.sessionManager.getCwd(),
|
|
3927
|
+
() => ({
|
|
3928
|
+
activeSkillState: this.getActiveSkillState(),
|
|
3929
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
3930
|
+
}),
|
|
3931
|
+
),
|
|
3932
|
+
),
|
|
3924
3933
|
);
|
|
3925
3934
|
}
|
|
3926
3935
|
|