@gajae-code/coding-agent 0.9.6 → 0.10.1
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 +58 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +47 -7
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +26 -7
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +25 -1
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/package.json +9 -9
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2881 -425
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/deep-interview/render-middleware.ts +67 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/gjc-runtime/launch-tmux.ts +702 -135
- package/src/gjc-runtime/session-state-sidecar.ts +756 -135
- package/src/gjc-runtime/team-runtime.ts +6 -0
- package/src/gjc-runtime/tmux-common.ts +26 -7
- package/src/gjc-runtime/tmux-gc.ts +52 -9
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/gjc-runtime/tmux-sessions.ts +871 -68
- package/src/internal-urls/docs-index.generated.ts +12 -11
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/gajae-pet-widget.ts +360 -0
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +13 -6
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-cli.ts +2 -0
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +355 -33
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -25,12 +25,30 @@ import {
|
|
|
25
25
|
} from "./html-format";
|
|
26
26
|
import { renderThreadedFrame } from "./threaded-render";
|
|
27
27
|
|
|
28
|
+
/** `ask_controls_v1` is a protocol version 3 wire token. Keep this local because
|
|
29
|
+
* `telegram-daemon.ts` imports this reference client. */
|
|
30
|
+
const REFERENCE_CLIENT_HELLO = {
|
|
31
|
+
type: "hello",
|
|
32
|
+
protocolVersion: 3,
|
|
33
|
+
capabilities: ["ask_controls_v1"],
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
28
36
|
/** One inline-keyboard button. */
|
|
29
37
|
export interface InlineButton {
|
|
30
38
|
text: string;
|
|
31
39
|
callback_data: string;
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
/** Typed action controls are protocol data, never inferred from option labels. */
|
|
43
|
+
export interface TelegramActionControl {
|
|
44
|
+
id: "navigation_forward";
|
|
45
|
+
kind: "navigation";
|
|
46
|
+
label: "Next" | "Done";
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type TelegramCallbackAnswer = number | string | { controlId: TelegramActionControl["id"] };
|
|
51
|
+
|
|
34
52
|
/** A rendered Telegram message for an `action_needed`. */
|
|
35
53
|
export interface RenderedMessage {
|
|
36
54
|
text: string;
|
|
@@ -51,10 +69,22 @@ export function decodeCallbackData(data: string): { id: string; index: number }
|
|
|
51
69
|
return { index: Number(m[1]), id: m[2]! };
|
|
52
70
|
}
|
|
53
71
|
|
|
72
|
+
/** Encode a typed control independently from option labels. */
|
|
73
|
+
export function encodeControlCallbackData(actionId: string, controlId: TelegramActionControl["id"]): string {
|
|
74
|
+
const encoded = `c:${controlId === "navigation_forward" ? "n" : controlId}:${actionId}`;
|
|
75
|
+
if (Buffer.byteLength(encoded, "utf8") > 64) throw new Error("control callback data exceeded Telegram limit");
|
|
76
|
+
return encoded;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function decodeControlCallbackData(data: string): { id: string; controlId: TelegramActionControl["id"] } | null {
|
|
80
|
+
const match = /^c:n:(.+)$/.exec(data);
|
|
81
|
+
return match ? { controlId: "navigation_forward", id: match[1]! } : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
54
84
|
export interface CallbackRoute {
|
|
55
85
|
sessionId: string;
|
|
56
86
|
actionId: string;
|
|
57
|
-
answer:
|
|
87
|
+
answer: TelegramCallbackAnswer;
|
|
58
88
|
}
|
|
59
89
|
|
|
60
90
|
export interface SerializedAliasTable {
|
|
@@ -78,7 +108,11 @@ function isCallbackRoute(value: unknown): value is CallbackRoute {
|
|
|
78
108
|
return (
|
|
79
109
|
typeof route.sessionId === "string" &&
|
|
80
110
|
typeof route.actionId === "string" &&
|
|
81
|
-
(typeof route.answer === "string" ||
|
|
111
|
+
(typeof route.answer === "string" ||
|
|
112
|
+
typeof route.answer === "number" ||
|
|
113
|
+
(typeof route.answer === "object" &&
|
|
114
|
+
route.answer !== null &&
|
|
115
|
+
(route.answer as { controlId?: unknown }).controlId === "navigation_forward"))
|
|
82
116
|
);
|
|
83
117
|
}
|
|
84
118
|
|
|
@@ -129,6 +163,7 @@ export function buildActionMessage(action: {
|
|
|
129
163
|
id: string;
|
|
130
164
|
question?: string;
|
|
131
165
|
options?: string[];
|
|
166
|
+
controls?: readonly TelegramActionControl[];
|
|
132
167
|
summary?: string;
|
|
133
168
|
}): RenderedMessage {
|
|
134
169
|
if (action.kind === "idle") {
|
|
@@ -137,9 +172,15 @@ export function buildActionMessage(action: {
|
|
|
137
172
|
}
|
|
138
173
|
const text = `❓ ${bold(action.question ?? "Question")}`;
|
|
139
174
|
const options = action.options ?? [];
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
175
|
+
const controls = (action.controls ?? []).filter(control => control.enabled);
|
|
176
|
+
if (options.length === 0 && controls.length === 0) return { text: `${text}\n\n(reply with text)` };
|
|
177
|
+
const body = options.length ? `${text}\n\n${numberedOptionList(options)}` : text;
|
|
178
|
+
const inline_keyboard = [
|
|
179
|
+
...(options.length ? buildCompactChoiceGrid(options, i => encodeCallbackData(action.id, i)) : []),
|
|
180
|
+
...controls.map(control => [
|
|
181
|
+
{ text: control.label, callback_data: encodeControlCallbackData(action.id, control.id) },
|
|
182
|
+
]),
|
|
183
|
+
];
|
|
143
184
|
return { text: body, inline_keyboard };
|
|
144
185
|
}
|
|
145
186
|
|
|
@@ -182,7 +223,7 @@ export async function sendTelegramHtmlChunks(
|
|
|
182
223
|
export interface ReplyFrame {
|
|
183
224
|
type: "reply";
|
|
184
225
|
id: string;
|
|
185
|
-
answer:
|
|
226
|
+
answer: TelegramCallbackAnswer;
|
|
186
227
|
token: string;
|
|
187
228
|
}
|
|
188
229
|
|
|
@@ -195,17 +236,15 @@ export function telegramUpdateToReply(
|
|
|
195
236
|
token: string,
|
|
196
237
|
latestPendingAskId: string | undefined,
|
|
197
238
|
): ReplyFrame | null {
|
|
198
|
-
const u = update as {
|
|
199
|
-
callback_query?: { data?: string };
|
|
200
|
-
message?: { text?: string };
|
|
201
|
-
};
|
|
239
|
+
const u = update as { callback_query?: { data?: string }; message?: { text?: string } };
|
|
202
240
|
if (u.callback_query?.data) {
|
|
203
241
|
const decoded = decodeCallbackData(u.callback_query.data);
|
|
204
242
|
if (decoded) return { type: "reply", id: decoded.id, answer: decoded.index, token };
|
|
243
|
+
const control = decodeControlCallbackData(u.callback_query.data);
|
|
244
|
+
if (control) return { type: "reply", id: control.id, answer: { controlId: control.controlId }, token };
|
|
205
245
|
}
|
|
206
|
-
if (u.message?.text && latestPendingAskId)
|
|
246
|
+
if (u.message?.text && latestPendingAskId)
|
|
207
247
|
return { type: "reply", id: latestPendingAskId, answer: u.message.text, token };
|
|
208
|
-
}
|
|
209
248
|
return null;
|
|
210
249
|
}
|
|
211
250
|
|
|
@@ -239,7 +278,10 @@ function updateChatId(update: TelegramUpdateShape): string | undefined {
|
|
|
239
278
|
return id === undefined || id === null ? undefined : String(id);
|
|
240
279
|
}
|
|
241
280
|
|
|
242
|
-
function routeWithAnswer(
|
|
281
|
+
function routeWithAnswer(
|
|
282
|
+
route: CallbackRoute | Omit<CallbackRoute, "answer">,
|
|
283
|
+
answer: TelegramCallbackAnswer,
|
|
284
|
+
): CallbackRoute {
|
|
243
285
|
return { sessionId: route.sessionId, actionId: route.actionId, answer };
|
|
244
286
|
}
|
|
245
287
|
|
|
@@ -320,8 +362,10 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
320
362
|
id?: string;
|
|
321
363
|
question?: string;
|
|
322
364
|
options?: string[];
|
|
365
|
+
controls?: TelegramActionControl[];
|
|
323
366
|
summary?: string;
|
|
324
367
|
reason?: string;
|
|
368
|
+
requiredCapabilities?: unknown;
|
|
325
369
|
};
|
|
326
370
|
if (msg.type === "action_needed" && msg.id) {
|
|
327
371
|
if (msg.kind === "ask") latestPendingAskId = msg.id;
|
|
@@ -330,9 +374,22 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
330
374
|
id: msg.id,
|
|
331
375
|
question: msg.question,
|
|
332
376
|
options: msg.options,
|
|
377
|
+
controls: msg.controls,
|
|
333
378
|
summary: msg.summary,
|
|
334
379
|
});
|
|
335
380
|
await sendTelegramHtmlChunks(send, opts.chatId, rendered.text, rendered.inline_keyboard);
|
|
381
|
+
} else if (msg.type === "action_unavailable") {
|
|
382
|
+
const requiredCapabilities = Array.isArray(msg.requiredCapabilities)
|
|
383
|
+
? msg.requiredCapabilities
|
|
384
|
+
.filter((capability): capability is string => typeof capability === "string")
|
|
385
|
+
.slice(0, 4)
|
|
386
|
+
.map(capability => capability.slice(0, 64))
|
|
387
|
+
: [];
|
|
388
|
+
console.warn(
|
|
389
|
+
`Telegram reference client: server withheld a controlled ask because this client lacks requiredCapabilities=[${requiredCapabilities.join(", ") || "unspecified"}].`,
|
|
390
|
+
);
|
|
391
|
+
// Diagnostic only: never turn it into a Telegram prompt or option buttons.
|
|
392
|
+
return;
|
|
336
393
|
} else if (msg.type === "action_resolved" && msg.id === latestPendingAskId) {
|
|
337
394
|
latestPendingAskId = undefined;
|
|
338
395
|
} else {
|
|
@@ -347,6 +404,9 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
347
404
|
};
|
|
348
405
|
|
|
349
406
|
let messageQueue = Promise.resolve();
|
|
407
|
+
ws.addEventListener("open", () => {
|
|
408
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(REFERENCE_CLIENT_HELLO));
|
|
409
|
+
});
|
|
350
410
|
ws.addEventListener("message", ev => {
|
|
351
411
|
const data = String(ev.data);
|
|
352
412
|
messageQueue = messageQueue.catch(() => undefined).then(() => handleServerMessage(data));
|
|
@@ -21,8 +21,16 @@ export interface TopicRecord {
|
|
|
21
21
|
identitySent: boolean;
|
|
22
22
|
/** Creation timestamp (ms epoch). */
|
|
23
23
|
createdAt: number;
|
|
24
|
-
/** Last applied
|
|
24
|
+
/** Last applied or observed Telegram topic title. */
|
|
25
25
|
name?: string;
|
|
26
|
+
/** Naming authority. Missing values are legacy daemon-owned records. */
|
|
27
|
+
nameOwner?: "user";
|
|
28
|
+
/** Whether a user-owned name still needs a best-effort Telegram re-assert. */
|
|
29
|
+
nameReconcilePending?: boolean;
|
|
30
|
+
/** Last accepted Telegram update id for a user-owned name. */
|
|
31
|
+
userNameUpdateId?: number;
|
|
32
|
+
/** Stable repo/branch identity used when topic names are user-owned or customized. */
|
|
33
|
+
identityKey?: string;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
/** Serialisable shape persisted to disk. */
|
|
@@ -48,13 +56,31 @@ export class TopicRegistry {
|
|
|
48
56
|
private readonly inflight = new Map<string, Promise<TopicRecord>>();
|
|
49
57
|
|
|
50
58
|
constructor(state: TopicRegistryState = emptyTopicRegistryState()) {
|
|
51
|
-
this.topics = new Map(
|
|
52
|
-
|
|
59
|
+
this.topics = new Map();
|
|
60
|
+
this.load(state);
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
/** Merge
|
|
63
|
+
/** Merge serialized state and normalize authority fields from older releases. */
|
|
56
64
|
load(state: TopicRegistryState): void {
|
|
57
|
-
for (const [sessionId,
|
|
65
|
+
for (const [sessionId, raw] of Object.entries(state.topics ?? {})) {
|
|
66
|
+
if (!raw || typeof raw.topicId !== "string") continue;
|
|
67
|
+
const hasValidUserAuthority =
|
|
68
|
+
raw.nameOwner === "user" &&
|
|
69
|
+
typeof raw.name === "string" &&
|
|
70
|
+
raw.name.trim().length > 0 &&
|
|
71
|
+
typeof raw.userNameUpdateId === "number" &&
|
|
72
|
+
Number.isSafeInteger(raw.userNameUpdateId) &&
|
|
73
|
+
raw.userNameUpdateId >= 0;
|
|
74
|
+
const record: TopicRecord = {
|
|
75
|
+
topicId: raw.topicId,
|
|
76
|
+
identitySent: raw.identitySent === true,
|
|
77
|
+
createdAt: typeof raw.createdAt === "number" ? raw.createdAt : 0,
|
|
78
|
+
...(typeof raw.name === "string" ? { name: raw.name } : {}),
|
|
79
|
+
...(hasValidUserAuthority ? { nameOwner: "user" as const } : {}),
|
|
80
|
+
...(hasValidUserAuthority && raw.nameReconcilePending === true ? { nameReconcilePending: true } : {}),
|
|
81
|
+
...(hasValidUserAuthority ? { userNameUpdateId: raw.userNameUpdateId } : {}),
|
|
82
|
+
...(typeof raw.identityKey === "string" ? { identityKey: raw.identityKey } : {}),
|
|
83
|
+
};
|
|
58
84
|
this.topics.set(sessionId, record);
|
|
59
85
|
this.byTopic.set(record.topicId, sessionId);
|
|
60
86
|
}
|
|
@@ -120,16 +146,67 @@ export class TopicRegistry {
|
|
|
120
146
|
return record ? !record.identitySent : true;
|
|
121
147
|
}
|
|
122
148
|
|
|
123
|
-
/**
|
|
149
|
+
/** Remember stable repo/branch identity independently of the displayed name. */
|
|
150
|
+
markIdentityKey(sessionId: string, identityKey: string): boolean {
|
|
151
|
+
const record = this.topics.get(sessionId);
|
|
152
|
+
if (!record || record.identityKey === identityKey) return false;
|
|
153
|
+
record.identityKey = identityKey;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Whether daemon identity reconciliation should apply `name`. */
|
|
124
158
|
needsRename(sessionId: string, name: string): boolean {
|
|
125
159
|
const record = this.topics.get(sessionId);
|
|
126
|
-
return record !== undefined && record.name !== name;
|
|
160
|
+
return record !== undefined && record.nameOwner !== "user" && record.name !== name;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The user-owned name that must be preserved, when one exists. */
|
|
164
|
+
userOwnedName(sessionId: string): string | undefined {
|
|
165
|
+
const record = this.topics.get(sessionId);
|
|
166
|
+
return record?.nameOwner === "user" ? record.name : undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** A user-owned name whose Telegram reconciliation is still pending. */
|
|
170
|
+
userNameToReconcile(sessionId: string): string | undefined {
|
|
171
|
+
const record = this.topics.get(sessionId);
|
|
172
|
+
return record?.nameOwner === "user" && record.nameReconcilePending ? record.name : undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Record an explicit Telegram-side user rename, rejecting stale update ids. */
|
|
176
|
+
markUserName(sessionId: string, name: string, updateId: number): "updated" | "duplicate" | "stale" {
|
|
177
|
+
const record = this.topics.get(sessionId);
|
|
178
|
+
if (!record) return "stale";
|
|
179
|
+
if (record.userNameUpdateId !== undefined && updateId < record.userNameUpdateId) return "stale";
|
|
180
|
+
if (record.userNameUpdateId === updateId) return "duplicate";
|
|
181
|
+
record.name = name;
|
|
182
|
+
record.nameOwner = "user";
|
|
183
|
+
record.nameReconcilePending = true;
|
|
184
|
+
record.userNameUpdateId = updateId;
|
|
185
|
+
return "updated";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Mark the matching preserved user name as reconciled with Telegram. */
|
|
189
|
+
markUserNameReconciled(sessionId: string, name: string): boolean {
|
|
190
|
+
const record = this.topics.get(sessionId);
|
|
191
|
+
if (record?.nameOwner !== "user" || record.name !== name || !record.nameReconcilePending) return false;
|
|
192
|
+
record.nameReconcilePending = false;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Restore retryable reconciliation after a failed pending-clear persistence. */
|
|
197
|
+
markUserNamePending(sessionId: string, name: string): boolean {
|
|
198
|
+
const record = this.topics.get(sessionId);
|
|
199
|
+
if (record?.nameOwner !== "user" || record.name !== name || record.nameReconcilePending) return false;
|
|
200
|
+
record.nameReconcilePending = true;
|
|
201
|
+
return true;
|
|
127
202
|
}
|
|
128
203
|
|
|
129
|
-
/** Commit a successfully-applied
|
|
204
|
+
/** Commit a successfully-applied daemon topic title. */
|
|
130
205
|
markNameApplied(sessionId: string, name: string): void {
|
|
131
206
|
const record = this.topics.get(sessionId);
|
|
132
|
-
if (record
|
|
207
|
+
if (!record || record.nameOwner === "user") return;
|
|
208
|
+
record.name = name;
|
|
209
|
+
record.nameReconcilePending = false;
|
|
133
210
|
}
|
|
134
211
|
|
|
135
212
|
/** Remove a session topic record after Telegram deletes the topic. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<system-reminder>IRC peers: {{roster}}</system-reminder>
|
|
@@ -34,6 +34,7 @@ You can reach other live agents via the `irc` tool. Your id is `{{ircSelfId}}`.
|
|
|
34
34
|
{{ircPeers}}
|
|
35
35
|
|
|
36
36
|
Use `irc` only when you need a quick answer from a peer; do not use it for long-form content. Address peers by id or use `"all"` to broadcast.
|
|
37
|
+
Use `irc` to DM `0-Main` on unexpected state instead of guessing; coordinate before changing shared files.
|
|
37
38
|
{{/if}}
|
|
38
39
|
[/COOP]
|
|
39
40
|
|
|
@@ -213,6 +213,7 @@ Delegate by default for multi-file changes, refactors, new features, tests, and
|
|
|
213
213
|
{{#has tools "subagent"}}- Use `{{toolRefs.subagent}}` to list, inspect, await with `timeout_ms`, or cancel detached task subagents.{{/has}}
|
|
214
214
|
- If an await timeout elapses, the subagent is still running; this is not a failure. Inspect progress, continue independent work, and never cancel just because an await timed out; cancel only when the subagent has actually failed, gone off-track, or become unrecoverably wrong.
|
|
215
215
|
{{#has tools "irc"}}- If live messaging is enabled, coordinate with running subagents through `{{toolRefs.irc}}`; cancellation is not a message channel.{{/has}}
|
|
216
|
+
{{#has tools "irc"}}- When a subagent's scope shifts or needs steering, check in through `{{toolRefs.irc}}` rather than guessing its state.{{/has}}
|
|
216
217
|
{{#has tools "job"}}- `{{toolRefs.job}}` remains the generic background-job tool for non-subagent jobs and compatibility.{{/has}}
|
|
217
218
|
</detached-subagents>
|
|
218
219
|
{{/has}}
|
package/src/prompts/tools/irc.md
CHANGED
|
@@ -14,7 +14,7 @@ You SHOULD reach for `irc` proactively when continuing alone is wasteful or wron
|
|
|
14
14
|
- **Unexpected state.** You hit something the original task did not describe — a missing file, a config that contradicts the assignment, an API behaving differently than you were told, a tool failing in a way that suggests the spec is wrong. DM `0-Main` (or the spawning agent) for guidance instead of guessing.
|
|
15
15
|
- **Blocked by another agent.** A peer holds the file/branch/resource you need, has already started the change you are about to make, or owns a decision you depend on. DM that peer (or broadcast to discover who) before duplicating or stepping on work.
|
|
16
16
|
- **Decision points outside your scope.** A genuine fork in the road that the assignment did not pre-decide (e.g. which of two viable APIs to use, whether to refactor adjacent code). Ask the requester rather than picking unilaterally.
|
|
17
|
-
- **Coordination opportunities.**
|
|
17
|
+
- **Coordination opportunities.** Before editing a shared file or relying on another agent's in-flight API, message the relevant peer; proactively share state that affects their work.
|
|
18
18
|
|
|
19
19
|
Do **not** use `irc` for: routine progress updates, things you can verify with a tool call, or questions whose answer is already in your assignment / repo / docs.
|
|
20
20
|
</conditions>
|
|
@@ -16,6 +16,7 @@ export type AgentKind = "main" | "sub";
|
|
|
16
16
|
export interface AgentRef {
|
|
17
17
|
id: string;
|
|
18
18
|
displayName: string;
|
|
19
|
+
rosterLabel?: string;
|
|
19
20
|
kind: AgentKind;
|
|
20
21
|
parentId?: string;
|
|
21
22
|
status: AgentStatus;
|
|
@@ -35,6 +36,7 @@ type RegistryListener = (event: RegistryEvent) => void;
|
|
|
35
36
|
export interface RegisterInput {
|
|
36
37
|
id: string;
|
|
37
38
|
displayName: string;
|
|
39
|
+
rosterLabel?: string;
|
|
38
40
|
kind: AgentKind;
|
|
39
41
|
parentId?: string;
|
|
40
42
|
session: AgentSession | null;
|
|
@@ -65,6 +67,7 @@ export class AgentRegistry {
|
|
|
65
67
|
const ref: AgentRef = {
|
|
66
68
|
id: input.id,
|
|
67
69
|
displayName: input.displayName,
|
|
70
|
+
rosterLabel: input.rosterLabel,
|
|
68
71
|
kind: input.kind,
|
|
69
72
|
parentId: input.parentId,
|
|
70
73
|
status: input.status ?? "running",
|
package/src/sdk.ts
CHANGED
|
@@ -94,6 +94,7 @@ import { createNotificationsExtension } from "./notifications";
|
|
|
94
94
|
import {
|
|
95
95
|
getNotificationConfig,
|
|
96
96
|
type NotificationConfig,
|
|
97
|
+
SPAWN_PROVENANCE_ENV,
|
|
97
98
|
shouldRegisterNotificationsExtension,
|
|
98
99
|
} from "./notifications/config";
|
|
99
100
|
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
@@ -242,6 +243,28 @@ function buildMcpNotificationBatchMessage(entries: McpNotificationEntry[]): Agen
|
|
|
242
243
|
};
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
function sanitizeRosterLabel(value: string): string {
|
|
247
|
+
const normalized = value
|
|
248
|
+
.replace(/[\p{Cc}]/gu, " ")
|
|
249
|
+
.replace(/\s+/g, " ")
|
|
250
|
+
.trim();
|
|
251
|
+
return normalized.length > 48 ? `${normalized.slice(0, 47)}…` : normalized;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function humanizeAgentTaskId(id: string): string {
|
|
255
|
+
return id
|
|
256
|
+
.replace(/([a-z\d])([A-Z])/g, "$1 $2")
|
|
257
|
+
.replace(/[-_]+/g, " ")
|
|
258
|
+
.trim();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function resolveAgentRosterLabel(label: string | undefined, agentId: string, displayName: string): string {
|
|
262
|
+
return (
|
|
263
|
+
sanitizeRosterLabel(label ?? "") ||
|
|
264
|
+
sanitizeRosterLabel(humanizeAgentTaskId(agentId)) ||
|
|
265
|
+
sanitizeRosterLabel(displayName)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
245
268
|
// Types
|
|
246
269
|
export interface CreateAgentSessionOptions {
|
|
247
270
|
/** Working directory for project-local discovery. Default: getProjectDir() */
|
|
@@ -334,6 +357,8 @@ export interface CreateAgentSessionOptions {
|
|
|
334
357
|
agentId?: string;
|
|
335
358
|
/** Display name for the agent in IRC. Default: "main" or "sub". */
|
|
336
359
|
agentDisplayName?: string;
|
|
360
|
+
/** Compact task label for hidden IRC roster reminders. */
|
|
361
|
+
agentRosterLabel?: string;
|
|
337
362
|
/** Optional restricted bash command prefixes for read-only role agents. */
|
|
338
363
|
bashAllowedPrefixes?: string[];
|
|
339
364
|
/** Restriction policy paired with bashAllowedPrefixes. */
|
|
@@ -1271,7 +1296,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1271
1296
|
const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
|
|
1272
1297
|
const isSubSession = taskDepth > 0 || Boolean(options.parentTaskPrefix) || Boolean(options.currentAgentType);
|
|
1273
1298
|
const resolvedAgentDisplayName = options.agentDisplayName ?? (isSubSession ? "sub" : "main");
|
|
1299
|
+
const resolvedAgentRosterLabel = resolveAgentRosterLabel(
|
|
1300
|
+
options.agentRosterLabel,
|
|
1301
|
+
resolvedAgentId,
|
|
1302
|
+
resolvedAgentDisplayName,
|
|
1303
|
+
);
|
|
1274
1304
|
const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
|
|
1305
|
+
let disposeLocalProtocolOverride: (() => void) | undefined;
|
|
1306
|
+
let localProtocolOverrideReleased = false;
|
|
1307
|
+
const releaseLocalProtocolOverride = (): void => {
|
|
1308
|
+
if (localProtocolOverrideReleased) return;
|
|
1309
|
+
localProtocolOverrideReleased = true;
|
|
1310
|
+
disposeLocalProtocolOverride?.();
|
|
1311
|
+
};
|
|
1275
1312
|
|
|
1276
1313
|
try {
|
|
1277
1314
|
const getActiveModelString = (): string | undefined => {
|
|
@@ -1401,7 +1438,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1401
1438
|
if (asyncJobManager) AsyncJobManager.setInstance(asyncJobManager);
|
|
1402
1439
|
}
|
|
1403
1440
|
if (options.localProtocolOptions) {
|
|
1404
|
-
LocalProtocolHandler.
|
|
1441
|
+
disposeLocalProtocolOverride = LocalProtocolHandler.installOverride(options.localProtocolOptions);
|
|
1405
1442
|
}
|
|
1406
1443
|
toolSession.getArtifactsDir = getArtifactsDir;
|
|
1407
1444
|
toolSession.agentOutputManager = new AgentOutputManager(
|
|
@@ -1579,6 +1616,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1579
1616
|
} catch {
|
|
1580
1617
|
notificationCfg = undefined;
|
|
1581
1618
|
}
|
|
1619
|
+
// Consume the GJC spawn-provenance marker: read it once, then remove it
|
|
1620
|
+
// from this process's env so it is never re-inherited by children this
|
|
1621
|
+
// session later spawns (marker is per-spawn, not dynastic — each GJC child
|
|
1622
|
+
// spawn site sets it explicitly). Suppression under `sessionScope=primary`
|
|
1623
|
+
// keeps auto-spawned children (team workers, harness RPC owners) silent
|
|
1624
|
+
// while explicit `/session_create` opt-in (GJC_NOTIFICATIONS=1) still wins.
|
|
1625
|
+
const spawnProvenance = process.env[SPAWN_PROVENANCE_ENV];
|
|
1626
|
+
const spawnedByGjc = typeof spawnProvenance === "string" && spawnProvenance.trim().length > 0;
|
|
1627
|
+
delete process.env[SPAWN_PROVENANCE_ENV];
|
|
1582
1628
|
if (
|
|
1583
1629
|
shouldRegisterNotificationsExtension({
|
|
1584
1630
|
env: process.env,
|
|
@@ -1586,6 +1632,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1586
1632
|
taskDepth,
|
|
1587
1633
|
parentTaskPrefix: options.parentTaskPrefix,
|
|
1588
1634
|
currentAgentType: options.currentAgentType,
|
|
1635
|
+
spawnedByGjc,
|
|
1589
1636
|
})
|
|
1590
1637
|
) {
|
|
1591
1638
|
inlineExtensions.push(api => createNotificationsExtension(api, { settings }));
|
|
@@ -2038,6 +2085,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2038
2085
|
agentRegistry.register({
|
|
2039
2086
|
id: resolvedAgentId,
|
|
2040
2087
|
displayName: resolvedAgentDisplayName,
|
|
2088
|
+
rosterLabel: resolvedAgentRosterLabel,
|
|
2041
2089
|
kind: isSubSession ? "sub" : "main",
|
|
2042
2090
|
parentId: options.parentTaskPrefix,
|
|
2043
2091
|
session: null,
|
|
@@ -2371,6 +2419,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2371
2419
|
} finally {
|
|
2372
2420
|
agentRegistry.unregister(resolvedAgentId);
|
|
2373
2421
|
unsubscribeCredentialDisabled?.();
|
|
2422
|
+
releaseLocalProtocolOverride();
|
|
2374
2423
|
}
|
|
2375
2424
|
};
|
|
2376
2425
|
}
|
|
@@ -2535,6 +2584,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2535
2584
|
logger.warn("Failed to clean up createAgentSession resources after startup error", {
|
|
2536
2585
|
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
2537
2586
|
});
|
|
2587
|
+
} finally {
|
|
2588
|
+
releaseLocalProtocolOverride();
|
|
2538
2589
|
}
|
|
2539
2590
|
throw error;
|
|
2540
2591
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Message, TextContent } from "@gajae-code/ai";
|
|
2
|
-
import type
|
|
2
|
+
import { type SessionContext, transferSessionMessageIdentity } from "../session/session-manager";
|
|
3
3
|
import { compileSecretRegex } from "./regex";
|
|
4
4
|
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -281,7 +281,9 @@ export function deobfuscateSessionContext(
|
|
|
281
281
|
): SessionContext {
|
|
282
282
|
if (!obfuscator?.hasSecrets()) return sessionContext;
|
|
283
283
|
const messages = obfuscator.deobfuscateObject(sessionContext.messages);
|
|
284
|
-
|
|
284
|
+
if (messages === sessionContext.messages) return sessionContext;
|
|
285
|
+
transferSessionMessageIdentity(sessionContext.messages, messages);
|
|
286
|
+
return { ...sessionContext, messages };
|
|
285
287
|
}
|
|
286
288
|
|
|
287
289
|
// ═══════════════════════════════════════════════════════════════════════════
|