@gajae-code/coding-agent 0.8.2 → 0.9.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 +53 -0
- package/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +119 -1
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/components/thinking-selector.d.ts +2 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +22 -0
- package/dist/types/notifications/index.d.ts +2 -0
- package/dist/types/notifications/lifecycle-commands.d.ts +1 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
- package/dist/types/notifications/telegram-daemon.d.ts +30 -0
- package/dist/types/notifications/telegram-reference.d.ts +7 -0
- package/dist/types/notifications/threaded-render.d.ts +14 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +43 -1
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/utils/pasted-image-path.d.ts +30 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/package.json +8 -11
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/config-cli.ts +27 -3
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +11 -9
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +26 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/config/keybindings.ts +1 -1
- package/src/config/settings-schema.ts +76 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
- package/src/defaults/gjc/skills/team/SKILL.md +16 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/gjc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +181 -219
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
- package/src/gjc-runtime/deep-interview-state.ts +31 -2
- package/src/gjc-runtime/launch-tmux.ts +6 -0
- package/src/gjc-runtime/session-state-sidecar.ts +208 -13
- package/src/gjc-runtime/state-writer.ts +15 -0
- package/src/gjc-runtime/team-runtime.ts +175 -19
- package/src/gjc-runtime/tmux-common.ts +1 -0
- package/src/gjc-runtime/ultragoal-guard.ts +55 -66
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1602 -143
- package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
- package/src/gjc-runtime/workflow-manifest.ts +74 -1
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +95 -4
- package/src/internal-urls/docs-index.generated.ts +18 -9
- package/src/lsp/client.ts +1 -0
- package/src/main.ts +43 -2
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/footer.ts +7 -2
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/thinking-selector.ts +10 -8
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/controllers/command-controller.ts +39 -2
- package/src/modes/controllers/event-controller.ts +77 -49
- package/src/modes/controllers/extension-ui-controller.ts +8 -1
- package/src/modes/controllers/input-controller.ts +13 -18
- package/src/modes/controllers/selector-controller.ts +41 -0
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/rpc/rpc-mode.ts +32 -12
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +17 -4
- package/src/notifications/config-commands.ts +20 -0
- package/src/notifications/config.ts +43 -4
- package/src/notifications/index.ts +105 -9
- package/src/notifications/lifecycle-commands.ts +37 -16
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +142 -0
- package/src/notifications/telegram-daemon-cli.ts +54 -7
- package/src/notifications/telegram-daemon-control.ts +2 -2
- package/src/notifications/telegram-daemon.ts +390 -56
- package/src/notifications/telegram-reference.ts +17 -0
- package/src/notifications/threaded-render.ts +45 -2
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +26 -19
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +6 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/sdk.ts +68 -30
- package/src/session/agent-session.ts +734 -107
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +59 -2
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +143 -1
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +125 -20
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +4 -3
- package/src/task/output-manager.ts +4 -2
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/utils/pasted-image-path.ts +170 -0
- package/src/web/scrapers/youtube.ts +24 -12
|
@@ -16,6 +16,7 @@ import { ToolExecutionComponent } from "../../modes/components/tool-execution";
|
|
|
16
16
|
import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
|
|
17
17
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
18
18
|
import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
|
|
19
|
+
import { completionNotifyDisabledByEnv } from "../../notifications/config";
|
|
19
20
|
import { summaryFromMessage } from "../../notifications/helpers";
|
|
20
21
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
21
22
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
@@ -23,11 +24,22 @@ import { isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
|
23
24
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
24
25
|
import { interruptHint } from "../shared";
|
|
25
26
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
27
|
+
import { consumeInjectedOptimisticSignature } from "../utils/injected-user-submission";
|
|
26
28
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
29
|
+
import { argsWithPartialJson } from "../utils/ui-helpers";
|
|
27
30
|
|
|
28
31
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
29
32
|
|
|
30
33
|
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
34
|
+
|
|
35
|
+
/** Test-only performance counters for advisory baseline tests. */
|
|
36
|
+
export const __eventControllerPerfCounters = {
|
|
37
|
+
messageUpdateContentVisits: 0,
|
|
38
|
+
reset(): void {
|
|
39
|
+
this.messageUpdateContentVisits = 0;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
31
43
|
const COMPLETION_NOTIFY_COMMAND_TIMEOUT_MS = 10_000;
|
|
32
44
|
|
|
33
45
|
interface CompletionNotifyPayload {
|
|
@@ -100,6 +112,8 @@ export class EventController {
|
|
|
100
112
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
101
113
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
102
114
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
115
|
+
#toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
|
|
116
|
+
#thinkingContentIndices = new Set<number>();
|
|
103
117
|
#handlers: AgentSessionEventHandlers;
|
|
104
118
|
|
|
105
119
|
constructor(private ctx: InteractiveModeContext) {
|
|
@@ -279,12 +293,17 @@ export class EventController {
|
|
|
279
293
|
const signature = `${textContent}\u0000${imageCount}`;
|
|
280
294
|
|
|
281
295
|
this.#resetReadGroup();
|
|
282
|
-
const
|
|
296
|
+
const wasSingleOptimistic = this.ctx.optimisticUserMessageSignature === signature;
|
|
297
|
+
const wasInjectedOptimistic = !wasSingleOptimistic && consumeInjectedOptimisticSignature(this.ctx, signature);
|
|
298
|
+
const wasOptimistic = wasSingleOptimistic || wasInjectedOptimistic;
|
|
283
299
|
const wasLocallySubmitted = this.ctx.locallySubmittedUserSignatures.delete(signature) || wasOptimistic;
|
|
284
300
|
if (!wasOptimistic) {
|
|
285
301
|
this.ctx.addMessageToChat(event.message);
|
|
286
302
|
}
|
|
287
|
-
|
|
303
|
+
// Only clear the single local slot when IT matched; an injected match is
|
|
304
|
+
// consumed from the counting Map above and must not clobber a coexisting
|
|
305
|
+
// pending local optimistic signature.
|
|
306
|
+
if (wasSingleOptimistic) {
|
|
288
307
|
this.ctx.optimisticUserMessageSignature = undefined;
|
|
289
308
|
}
|
|
290
309
|
|
|
@@ -307,12 +326,14 @@ export class EventController {
|
|
|
307
326
|
} else if (event.message.role === "assistant") {
|
|
308
327
|
this.#lastThinkingCount = 0;
|
|
309
328
|
this.#resetReadGroup();
|
|
329
|
+
this.#toolIntentCache.clear();
|
|
330
|
+
this.#thinkingContentIndices.clear();
|
|
310
331
|
this.ctx.streamingComponent = new AssistantMessageComponent(undefined, this.ctx.hideThinkingBlock, () =>
|
|
311
332
|
this.ctx.ui.requestRender(),
|
|
312
333
|
);
|
|
313
334
|
this.ctx.streamingMessage = event.message;
|
|
314
335
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
315
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
336
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
316
337
|
this.ctx.ui.requestRender();
|
|
317
338
|
}
|
|
318
339
|
}
|
|
@@ -375,46 +396,53 @@ export class EventController {
|
|
|
375
396
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
376
397
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
377
398
|
this.ctx.streamingMessage = event.message;
|
|
378
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
400
|
+
const contentIndex = event.assistantMessageEvent?.contentIndex;
|
|
401
|
+
const changedContent =
|
|
402
|
+
typeof contentIndex === "number" && contentIndex >= 0
|
|
403
|
+
? this.ctx.streamingMessage.content[contentIndex]
|
|
404
|
+
: undefined;
|
|
405
|
+
const contentsToProcess = changedContent ? [changedContent] : this.ctx.streamingMessage.content;
|
|
406
|
+
|
|
407
|
+
if (changedContent?.type === "thinking" && changedContent.thinking.trim()) {
|
|
408
|
+
if (typeof contentIndex === "number") this.#thinkingContentIndices.add(contentIndex);
|
|
409
|
+
const thinkingCount = this.#thinkingContentIndices.size;
|
|
410
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
411
|
+
this.#resetReadGroup();
|
|
412
|
+
this.#lastThinkingCount = thinkingCount;
|
|
413
|
+
}
|
|
414
|
+
} else if (!changedContent) {
|
|
415
|
+
const thinkingCount = this.ctx.streamingMessage.content.filter(
|
|
416
|
+
content => content.type === "thinking" && content.thinking.trim(),
|
|
417
|
+
).length;
|
|
418
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
419
|
+
this.#resetReadGroup();
|
|
420
|
+
this.#lastThinkingCount = thinkingCount;
|
|
421
|
+
}
|
|
386
422
|
}
|
|
387
423
|
|
|
388
|
-
for (const content of
|
|
424
|
+
for (const content of contentsToProcess) {
|
|
425
|
+
__eventControllerPerfCounters.messageUpdateContentVisits += 1;
|
|
389
426
|
if (content.type !== "toolCall") continue;
|
|
390
427
|
if (content.name === "read") {
|
|
391
|
-
if (!readArgsHaveTarget(content.arguments))
|
|
392
|
-
// Args still streaming — defer until path is parseable so we can route to the
|
|
393
|
-
// read group (regular files) vs ToolExecutionComponent (internal URLs).
|
|
394
|
-
// Creating either component now would lock the read into the wrong shape.
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
428
|
+
if (!readArgsHaveTarget(content.arguments)) continue;
|
|
397
429
|
if (!readArgsTargetInternalUrl(content.arguments)) {
|
|
398
430
|
this.#trackReadToolCall(content.id, content.arguments);
|
|
399
431
|
const component = this.ctx.pendingTools.get(content.id);
|
|
400
|
-
if (component)
|
|
401
|
-
|
|
402
|
-
} else {
|
|
432
|
+
if (component) component.updateArgs(content.arguments, content.id);
|
|
433
|
+
else {
|
|
403
434
|
const group = this.#getReadGroup();
|
|
404
435
|
group.updateArgs(content.arguments, content.id);
|
|
405
436
|
this.ctx.pendingTools.set(content.id, group);
|
|
406
437
|
}
|
|
407
438
|
continue;
|
|
408
439
|
}
|
|
409
|
-
// Internal URL read falls through to ToolExecutionComponent below.
|
|
410
440
|
}
|
|
411
441
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
? { ...content.arguments, __partialJson: content.partialJson }
|
|
417
|
-
: content.arguments;
|
|
442
|
+
const renderArgs = argsWithPartialJson(
|
|
443
|
+
content.arguments,
|
|
444
|
+
"partialJson" in content ? content.partialJson : undefined,
|
|
445
|
+
);
|
|
418
446
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
419
447
|
this.#resetReadGroup();
|
|
420
448
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
@@ -437,32 +465,30 @@ export class EventController {
|
|
|
437
465
|
this.ctx.chatContainer.addChild(component);
|
|
438
466
|
this.ctx.pendingTools.set(content.id, component);
|
|
439
467
|
} else {
|
|
440
|
-
|
|
441
|
-
if (component) {
|
|
442
|
-
component.updateArgs(renderArgs, content.id);
|
|
443
|
-
}
|
|
468
|
+
this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
|
|
444
469
|
}
|
|
445
|
-
}
|
|
446
470
|
|
|
447
|
-
// Update working message with intent from streamed tool arguments
|
|
448
|
-
for (const content of this.ctx.streamingMessage.content) {
|
|
449
|
-
if (content.type !== "toolCall") continue;
|
|
450
471
|
const args = content.arguments;
|
|
451
472
|
if (!args || typeof args !== "object") continue;
|
|
473
|
+
let intent: string | undefined;
|
|
452
474
|
if (INTENT_FIELD in args) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
475
|
+
intent = String(args[INTENT_FIELD]);
|
|
476
|
+
} else {
|
|
477
|
+
const cached = this.#toolIntentCache.get(content.id);
|
|
478
|
+
if (cached?.args === args) intent = cached.intent;
|
|
479
|
+
else {
|
|
480
|
+
const tool = this.ctx.session.getToolByName(content.name);
|
|
481
|
+
if (typeof tool?.intent === "function") {
|
|
482
|
+
try {
|
|
483
|
+
intent = tool.intent(args as never)?.trim();
|
|
484
|
+
} catch {
|
|
485
|
+
// intent function must never break the UI
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
this.#toolIntentCache.set(content.id, { args, intent });
|
|
462
489
|
}
|
|
463
|
-
} catch {
|
|
464
|
-
// intent function must never break the UI
|
|
465
490
|
}
|
|
491
|
+
if (intent) this.#updateWorkingMessageFromIntent(intent);
|
|
466
492
|
}
|
|
467
493
|
|
|
468
494
|
this.ctx.ui.requestRender();
|
|
@@ -495,9 +521,9 @@ export class EventController {
|
|
|
495
521
|
// display only — does NOT mutate the persisted message's stopReason
|
|
496
522
|
// (the marker on errorMessage drives replay-side suppression).
|
|
497
523
|
const msgWithoutAbort = { ...this.ctx.streamingMessage, stopReason: "stop" as const };
|
|
498
|
-
this.ctx.streamingComponent.updateContent(msgWithoutAbort);
|
|
524
|
+
this.ctx.streamingComponent.updateContent(msgWithoutAbort, { streaming: false });
|
|
499
525
|
} else {
|
|
500
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
526
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: false });
|
|
501
527
|
}
|
|
502
528
|
|
|
503
529
|
if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
|
|
@@ -941,6 +967,8 @@ export class EventController {
|
|
|
941
967
|
}
|
|
942
968
|
|
|
943
969
|
sendCompletionNotification(): void {
|
|
970
|
+
// Per-run hard opt-out (env, config-untouched, child-inheritable): GJC_NOTIFY=off.
|
|
971
|
+
if (completionNotifyDisabledByEnv(process.env)) return;
|
|
944
972
|
const isBackgrounded = this.ctx.isBackgrounded !== false;
|
|
945
973
|
const notify = settings.get("completion.notify");
|
|
946
974
|
if (notify === "off") return;
|
|
@@ -23,6 +23,7 @@ import { HookSelectorComponent } from "../../modes/components/hook-selector";
|
|
|
23
23
|
import { getAvailableThemesWithPaths, getThemeByName, setTheme, type Theme, theme } from "../../modes/theme/theme";
|
|
24
24
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
25
25
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
26
|
+
import { applyInjectedUserSubmission } from "../utils/injected-user-submission";
|
|
26
27
|
import { classifyHookSelectorBellEvent, ringTerminalBell } from "../utils/terminal-bell";
|
|
27
28
|
|
|
28
29
|
const MAX_WIDGET_LINES = 10;
|
|
@@ -954,7 +955,13 @@ export class ExtensionUiController {
|
|
|
954
955
|
}
|
|
955
956
|
|
|
956
957
|
#sendExtensionUserMessage: SendUserMessageHandler = (content, options) => {
|
|
957
|
-
|
|
958
|
+
// Compute queued BEFORE send: prompt() may flip session.isStreaming synchronously.
|
|
959
|
+
const queued = Boolean(options?.deliverAs) || this.ctx.session.isStreaming;
|
|
960
|
+
// Call send first so the busy/queued path finds the session queue populated
|
|
961
|
+
// (queueSteer/queueFollowUp push synchronously) before refreshing pending display.
|
|
962
|
+
const send = this.ctx.session.sendUserMessage(content, options);
|
|
963
|
+
applyInjectedUserSubmission(this.ctx, { content, queued });
|
|
964
|
+
send.catch((err: unknown) => {
|
|
958
965
|
this.ctx.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
959
966
|
});
|
|
960
967
|
};
|
|
@@ -18,6 +18,7 @@ import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
|
18
18
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
19
19
|
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
20
20
|
import { resizeImage } from "../../utils/image-resize";
|
|
21
|
+
import { resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
21
22
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
22
23
|
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
23
24
|
|
|
@@ -26,8 +27,6 @@ interface Expandable {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
29
|
-
const CLIPBOARD_TEMP_IMAGE_FILE_PATTERN = /^clipboard-\d{4}-\d{2}-\d{2}-\d{6}-[A-Za-z0-9]+\.(?:png|jpe?g|gif|webp)$/i;
|
|
30
|
-
const MACOS_CLIPBOARD_TEMP_DIR_PATTERN = /^\/var\/folders\/[^/]+\/[^/]+\/T$/;
|
|
31
30
|
|
|
32
31
|
function isExpandable(obj: unknown): obj is Expandable {
|
|
33
32
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
@@ -1061,10 +1060,15 @@ export class InputController {
|
|
|
1061
1060
|
}
|
|
1062
1061
|
|
|
1063
1062
|
handleTextPaste(text: string): boolean | Promise<boolean> {
|
|
1064
|
-
const imagePath = this
|
|
1063
|
+
const imagePath = resolvePastedImagePath(text, { cwd: this.ctx.sessionManager.getCwd() });
|
|
1065
1064
|
return imagePath ? this.#attachPastedImagePath(imagePath) : false;
|
|
1066
1065
|
}
|
|
1067
1066
|
|
|
1067
|
+
/**
|
|
1068
|
+
* Returns `false` on every failure path so the editor replays the original
|
|
1069
|
+
* bracketed paste — the raw path text must never be lost when attachment
|
|
1070
|
+
* is impossible (unsupported content, oversized image, load error).
|
|
1071
|
+
*/
|
|
1068
1072
|
async #attachPastedImagePath(imagePath: string): Promise<boolean> {
|
|
1069
1073
|
try {
|
|
1070
1074
|
const image = await loadImageInput({
|
|
@@ -1073,8 +1077,8 @@ export class InputController {
|
|
|
1073
1077
|
autoResize: this.ctx.settings.get("images.autoResize"),
|
|
1074
1078
|
});
|
|
1075
1079
|
if (!image) {
|
|
1076
|
-
this.ctx.showStatus("Unsupported pasted
|
|
1077
|
-
return
|
|
1080
|
+
this.ctx.showStatus("Unsupported pasted image file");
|
|
1081
|
+
return false;
|
|
1078
1082
|
}
|
|
1079
1083
|
|
|
1080
1084
|
this.ctx.pendingImages.push({
|
|
@@ -1089,22 +1093,13 @@ export class InputController {
|
|
|
1089
1093
|
} catch (error) {
|
|
1090
1094
|
if (error instanceof ImageInputTooLargeError) {
|
|
1091
1095
|
this.ctx.showStatus(error.message);
|
|
1092
|
-
return
|
|
1096
|
+
return false;
|
|
1093
1097
|
}
|
|
1094
|
-
this.ctx.showStatus("Failed to attach pasted
|
|
1095
|
-
return
|
|
1098
|
+
this.ctx.showStatus("Failed to attach pasted image");
|
|
1099
|
+
return false;
|
|
1096
1100
|
}
|
|
1097
1101
|
}
|
|
1098
1102
|
|
|
1099
|
-
#getPastedImagePathCandidate(text: string): string | undefined {
|
|
1100
|
-
const resolvedPath = path.resolve(text.trim());
|
|
1101
|
-
const parentDir = path.dirname(resolvedPath);
|
|
1102
|
-
const isClipboardTempPath =
|
|
1103
|
-
(parentDir === "/tmp" || MACOS_CLIPBOARD_TEMP_DIR_PATTERN.test(parentDir)) &&
|
|
1104
|
-
CLIPBOARD_TEMP_IMAGE_FILE_PATTERN.test(path.basename(resolvedPath));
|
|
1105
|
-
return isClipboardTempPath ? resolvedPath : undefined;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
1103
|
#nextImagePlaceholder(): string {
|
|
1109
1104
|
return `[image ${this.ctx.pendingImages.length}]`;
|
|
1110
1105
|
}
|
|
@@ -1308,7 +1303,7 @@ export class InputController {
|
|
|
1308
1303
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
|
1309
1304
|
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
1310
1305
|
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
|
1311
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
1306
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
1312
1307
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
1313
1308
|
}
|
|
1314
1309
|
|
|
@@ -88,6 +88,7 @@ import { SessionSelectorComponent } from "../components/session-selector";
|
|
|
88
88
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
89
89
|
import type { StatusLineSettings } from "../components/status-line";
|
|
90
90
|
import { ThemeSelectorComponent } from "../components/theme-selector";
|
|
91
|
+
import { ThinkingSelectorComponent } from "../components/thinking-selector";
|
|
91
92
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
92
93
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
93
94
|
import { UserMessageSelectorComponent } from "../components/user-message-selector";
|
|
@@ -431,6 +432,46 @@ export class SelectorController {
|
|
|
431
432
|
});
|
|
432
433
|
}
|
|
433
434
|
|
|
435
|
+
showEffortSelector(): void {
|
|
436
|
+
const availableLevels = [
|
|
437
|
+
ThinkingLevel.Inherit,
|
|
438
|
+
ThinkingLevel.Off,
|
|
439
|
+
...this.ctx.session.getAvailableThinkingLevels(),
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
this.showSelector(done => {
|
|
443
|
+
const selector = new ThinkingSelectorComponent(
|
|
444
|
+
this.ctx.session.thinkingLevel,
|
|
445
|
+
availableLevels,
|
|
446
|
+
level => {
|
|
447
|
+
done();
|
|
448
|
+
|
|
449
|
+
const configuredDefault = this.ctx.settings.get("defaultThinkingLevel");
|
|
450
|
+
const levelToApply = level === ThinkingLevel.Inherit ? configuredDefault : level;
|
|
451
|
+
this.ctx.session.setThinkingLevel(levelToApply, false);
|
|
452
|
+
const effectiveLevel = this.ctx.session.thinkingLevel ?? ThinkingLevel.Off;
|
|
453
|
+
const requestedLabel =
|
|
454
|
+
level === ThinkingLevel.Inherit ? `${level} (configured default: ${configuredDefault})` : level;
|
|
455
|
+
const clampedSuffix =
|
|
456
|
+
effectiveLevel === levelToApply ? "" : ` Requested ${levelToApply}; effective ${effectiveLevel}.`;
|
|
457
|
+
|
|
458
|
+
this.ctx.statusLine.invalidate();
|
|
459
|
+
this.ctx.updateEditorBorderColor();
|
|
460
|
+
this.ctx.updateEditorTopBorder();
|
|
461
|
+
this.ctx.ui.requestRender();
|
|
462
|
+
this.ctx.showStatus(
|
|
463
|
+
`Reasoning effort set to ${requestedLabel}. Effective effort: ${effectiveLevel}.${clampedSuffix}`,
|
|
464
|
+
);
|
|
465
|
+
},
|
|
466
|
+
() => {
|
|
467
|
+
done();
|
|
468
|
+
this.ctx.ui.requestRender();
|
|
469
|
+
},
|
|
470
|
+
);
|
|
471
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
434
475
|
showSettingsSelector(): void {
|
|
435
476
|
getAvailableThemes().then(availableThemes => {
|
|
436
477
|
this.showSelector(done => {
|
|
@@ -118,7 +118,7 @@ import { UiHelpers } from "./utils/ui-helpers";
|
|
|
118
118
|
|
|
119
119
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
120
120
|
const COMPOSER_NEWLINE_HINT = process.platform === "win32" ? "Alt+Enter/Ctrl+J" : "Shift+Enter/Ctrl+J";
|
|
121
|
-
const DEFAULT_COMPOSER_PLACEHOLDER = `Type your message... ${COMPOSER_NEWLINE_HINT}: New line · Ctrl+C: Clear · Shift+Tab: Reasoning`;
|
|
121
|
+
export const DEFAULT_COMPOSER_PLACEHOLDER = `Type your message... ${COMPOSER_NEWLINE_HINT}: New line · Ctrl+C: Clear · Ctrl+R: Search history · Shift+Tab: Reasoning`;
|
|
122
122
|
const WELCOME_RESERVED_CONTAINER_CHILD_LIMIT = 8;
|
|
123
123
|
const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
124
124
|
alt: "Alt",
|
|
@@ -163,6 +163,7 @@ function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
|
163
163
|
editor.setInputPrefix(getDefaultInputPrefix());
|
|
164
164
|
editor.setPlaceholder(DEFAULT_COMPOSER_PLACEHOLDER);
|
|
165
165
|
editor.setPaddingX(1);
|
|
166
|
+
editor.setRightGutterWidth(1);
|
|
166
167
|
editor.setTopBorder(undefined);
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -329,6 +330,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
329
330
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
330
331
|
optimisticUserMessageSignature: string | undefined = undefined;
|
|
331
332
|
locallySubmittedUserSignatures: Set<string> = new Set();
|
|
333
|
+
optimisticInjectedSignatures: Map<string, number> = new Map();
|
|
332
334
|
#pendingSubmittedInput: SubmittedUserInput | undefined;
|
|
333
335
|
#pendingSubmissionDispose: (() => void) | undefined;
|
|
334
336
|
lastSigintTime = 0;
|
|
@@ -1715,6 +1717,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1715
1717
|
planContent,
|
|
1716
1718
|
finalPlanFilePath: options.finalPlanFilePath,
|
|
1717
1719
|
contextPreserved: options.preserveContext === true,
|
|
1720
|
+
tools: this.session.getActiveToolNames(),
|
|
1718
1721
|
});
|
|
1719
1722
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
1720
1723
|
}
|
|
@@ -2206,6 +2209,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2206
2209
|
message => renderWorkingMessage(message, this.#getWorkingMessageAccent()),
|
|
2207
2210
|
this.#defaultWorkingMessage,
|
|
2208
2211
|
getSymbolTheme().spinnerFrames,
|
|
2212
|
+
{ timeDependentColor: true },
|
|
2209
2213
|
);
|
|
2210
2214
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
2211
2215
|
}
|
|
@@ -2363,6 +2367,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2363
2367
|
return this.#commandController.handleClearCommand();
|
|
2364
2368
|
}
|
|
2365
2369
|
|
|
2370
|
+
handleContextClearCommand(): Promise<void> {
|
|
2371
|
+
this.#prepareSessionSwitch();
|
|
2372
|
+
return this.#commandController.handleContextClearCommand();
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2366
2375
|
handleDropCommand(): Promise<void> {
|
|
2367
2376
|
this.#prepareSessionSwitch();
|
|
2368
2377
|
return this.#commandController.handleDropCommand();
|
|
@@ -2552,6 +2561,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2552
2561
|
this.#selectorController.showModelSelector(options);
|
|
2553
2562
|
}
|
|
2554
2563
|
|
|
2564
|
+
showEffortSelector(): void {
|
|
2565
|
+
this.#selectorController.showEffortSelector();
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2555
2568
|
showProviderOnboarding(): void {
|
|
2556
2569
|
this.#selectorController.showProviderOnboarding();
|
|
2557
2570
|
}
|
|
@@ -22,8 +22,13 @@ import { type Theme, theme } from "../../modes/theme/theme";
|
|
|
22
22
|
import type { AgentSession } from "../../session/agent-session";
|
|
23
23
|
import { initializeExtensions } from "../runtime-init";
|
|
24
24
|
import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
|
|
25
|
-
import {
|
|
26
|
-
|
|
25
|
+
import {
|
|
26
|
+
AgentWireCompactEventEncoder,
|
|
27
|
+
AgentWireFrameSequencer,
|
|
28
|
+
toAgentWireCompactEventFrame,
|
|
29
|
+
toAgentWireEventFrame,
|
|
30
|
+
} from "../shared/agent-wire/event-envelope";
|
|
31
|
+
import { rpcError as error, rpcSuccess } from "../shared/agent-wire/responses";
|
|
27
32
|
import { registerRpcSession, unregisterRpcSession } from "../shared/agent-wire/session-registry";
|
|
28
33
|
import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
|
|
29
34
|
import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
|
|
@@ -168,15 +173,9 @@ function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "e
|
|
|
168
173
|
return "info";
|
|
169
174
|
}
|
|
170
175
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`RPC --listen refused: a live server is already listening on ${socketPath}. ` +
|
|
175
|
-
"Stop it first or choose a different --listen path.",
|
|
176
|
-
);
|
|
177
|
-
this.name = "RpcListenRefusedError";
|
|
178
|
-
}
|
|
179
|
-
}
|
|
176
|
+
// Thrown by prepareRpcSocketPath when the --listen path has a live owner;
|
|
177
|
+
// re-exported here because main.ts catches it at the RPC launch boundary.
|
|
178
|
+
export { RpcListenRefusedError } from "./rpc-socket-security";
|
|
180
179
|
|
|
181
180
|
/**
|
|
182
181
|
* Probe whether a unix-domain socket path has a live server accepting
|
|
@@ -297,6 +296,7 @@ export async function runRpcMode(
|
|
|
297
296
|
output({ type: "ready" });
|
|
298
297
|
}
|
|
299
298
|
const emitRpcTitles = shouldEmitRpcTitles();
|
|
299
|
+
const rpcCapabilities = { compactMessageUpdate: false };
|
|
300
300
|
const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
301
301
|
|
|
302
302
|
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
@@ -636,8 +636,13 @@ export async function runRpcMode(
|
|
|
636
636
|
// Output all agent events as canonical agent-wire `event` frames (docs/rpc.md):
|
|
637
637
|
// { type:"event", protocol_version, session_id, seq, frame_id, payload:{ event_type, event } }.
|
|
638
638
|
const eventSequencer = new AgentWireFrameSequencer(session.sessionId);
|
|
639
|
+
const compactEventEncoder = new AgentWireCompactEventEncoder(eventSequencer);
|
|
639
640
|
session.subscribe(event => {
|
|
640
|
-
output(
|
|
641
|
+
output(
|
|
642
|
+
rpcCapabilities.compactMessageUpdate
|
|
643
|
+
? toAgentWireCompactEventFrame(event, compactEventEncoder)
|
|
644
|
+
: toAgentWireEventFrame(event, eventSequencer),
|
|
645
|
+
);
|
|
641
646
|
});
|
|
642
647
|
|
|
643
648
|
// Handle a single command through the shared agent-wire dispatcher so RPC
|
|
@@ -710,6 +715,21 @@ export async function runRpcMode(
|
|
|
710
715
|
hostUriBridge.handleResult(parsed);
|
|
711
716
|
return;
|
|
712
717
|
}
|
|
718
|
+
if ((parsed as RpcCommand).type === "set_capabilities") {
|
|
719
|
+
// RPC has no startup handshake; clients negotiate optional event-shape capabilities
|
|
720
|
+
// with this explicit command. Defaults remain legacy/full-frame until accepted here.
|
|
721
|
+
const command = parsed as Extract<RpcCommand, { type: "set_capabilities" }>;
|
|
722
|
+
const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
|
|
723
|
+
const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
|
|
724
|
+
rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
|
|
725
|
+
output(
|
|
726
|
+
rpcSuccess(command.id, "set_capabilities", {
|
|
727
|
+
acceptedCapabilities,
|
|
728
|
+
unsupported: requested.filter(capability => capability !== "compact_message_update"),
|
|
729
|
+
}),
|
|
730
|
+
);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
713
733
|
// Ordered commands run through a serial chain to preserve causal order; the
|
|
714
734
|
// reader never blocks, so cancellation commands stay responsive even while a
|
|
715
735
|
// long command is in flight (issue 13).
|
|
@@ -9,6 +9,18 @@ export class RpcSocketSecurityError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// The launch boundary in main.ts catches exactly this class to turn a
|
|
13
|
+
// duplicate --listen into a clean stderr diagnostic + non-zero exit (issue 19).
|
|
14
|
+
export class RpcListenRefusedError extends Error {
|
|
15
|
+
constructor(socketPath: string) {
|
|
16
|
+
super(
|
|
17
|
+
`RPC --listen refused: a live server is already listening on ${socketPath}. ` +
|
|
18
|
+
"Stop it first or choose a different --listen path.",
|
|
19
|
+
);
|
|
20
|
+
this.name = "RpcListenRefusedError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
const unsafeBits = 0o077;
|
|
13
25
|
|
|
14
26
|
function currentUid(): number | undefined {
|
|
@@ -53,7 +65,7 @@ export async function prepareRpcSocketPath(socketPath: string): Promise<void> {
|
|
|
53
65
|
if (!existing.isSocket()) throw new RpcSocketSecurityError(`RPC socket path is not a socket: ${socketPath}`);
|
|
54
66
|
assertPrivateMode(existing.mode, socketPath);
|
|
55
67
|
if (await probeUnixSocketAlive(socketPath)) {
|
|
56
|
-
throw new
|
|
68
|
+
throw new RpcListenRefusedError(socketPath);
|
|
57
69
|
}
|
|
58
70
|
await fs.unlink(socketPath);
|
|
59
71
|
}
|
|
@@ -13,6 +13,7 @@ import type { SessionStats } from "../../session/agent-session";
|
|
|
13
13
|
import type { TodoPhase } from "../../tools/todo-write";
|
|
14
14
|
|
|
15
15
|
export type RpcGetStateInclude = "tools" | "dumpTools" | "systemPrompt";
|
|
16
|
+
export type RpcCapability = "compact_message_update";
|
|
16
17
|
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// RPC Commands (stdin)
|
|
@@ -33,6 +34,7 @@ export type RpcCommand =
|
|
|
33
34
|
| { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
|
|
34
35
|
| { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
|
|
35
36
|
| { id?: string; type: "get_pending_workflow_gates" }
|
|
37
|
+
| { id?: string; type: "set_capabilities"; capabilities: RpcCapability[] }
|
|
36
38
|
|
|
37
39
|
// Model
|
|
38
40
|
| { id?: string; type: "set_model"; provider: string; modelId: string }
|
|
@@ -140,6 +142,13 @@ export type RpcResponse =
|
|
|
140
142
|
success: true;
|
|
141
143
|
data: { gates: RpcWorkflowGate[] };
|
|
142
144
|
}
|
|
145
|
+
| {
|
|
146
|
+
id?: string;
|
|
147
|
+
type: "response";
|
|
148
|
+
command: "set_capabilities";
|
|
149
|
+
success: true;
|
|
150
|
+
data: { acceptedCapabilities: RpcCapability[]; unsupported: string[] };
|
|
151
|
+
}
|
|
143
152
|
|
|
144
153
|
// Model
|
|
145
154
|
| {
|
|
@@ -130,6 +130,10 @@ export function isRpcCommand(value: unknown): value is RpcCommand {
|
|
|
130
130
|
return Array.isArray(value.tools) && value.tools.every(hostToolDefinition);
|
|
131
131
|
case "set_host_uri_schemes":
|
|
132
132
|
return Array.isArray(value.schemes) && value.schemes.every(hostUriScheme);
|
|
133
|
+
case "set_capabilities":
|
|
134
|
+
return (
|
|
135
|
+
Array.isArray(value.capabilities) && value.capabilities.every(capability => typeof capability === "string")
|
|
136
|
+
);
|
|
133
137
|
case "set_model":
|
|
134
138
|
return stringField(value, "provider") && stringField(value, "modelId");
|
|
135
139
|
case "set_thinking_level":
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* an `AgentSessionEvent` variant fails typecheck until it is registered, and so
|
|
14
14
|
* conformance tests can assert fixture coverage equals the registry exactly.
|
|
15
15
|
*/
|
|
16
|
+
|
|
17
|
+
import type { AssistantMessageEvent } from "@gajae-code/ai";
|
|
16
18
|
import type { AgentSessionEvent } from "../../../session/agent-session";
|
|
17
19
|
|
|
18
20
|
/** Wire protocol version. Bump on breaking envelope/semantic changes. */
|
|
@@ -68,6 +70,25 @@ export interface AgentWireEventPayload {
|
|
|
68
70
|
event_type: AgentWireEventType;
|
|
69
71
|
event: AgentSessionEvent;
|
|
70
72
|
}
|
|
73
|
+
export type AgentWireCompactAssistantMessageEvent = AssistantMessageEvent extends infer TEvent
|
|
74
|
+
? TEvent extends { type: string }
|
|
75
|
+
? Omit<TEvent, "partial" | "message" | "error">
|
|
76
|
+
: never
|
|
77
|
+
: never;
|
|
78
|
+
|
|
79
|
+
export interface AgentWireCompactMessageUpdatePayload {
|
|
80
|
+
event_type: "message_update";
|
|
81
|
+
/** Compact RPC opt-in payload: delta-only update plus minimal routing metadata. */
|
|
82
|
+
compact: true;
|
|
83
|
+
message_id: string | null;
|
|
84
|
+
content_index: number | null;
|
|
85
|
+
assistantMessageEvent: AgentWireCompactAssistantMessageEvent;
|
|
86
|
+
/** Full message checkpoint, snapshotted synchronously when this frame is enqueued. */
|
|
87
|
+
checkpoint_message?: Extract<AgentSessionEvent, { type: "message_update" }>["message"];
|
|
88
|
+
checkpoint_reason?: "periodic";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type AgentWireEventFramePayload = AgentWireEventPayload | AgentWireCompactMessageUpdatePayload;
|
|
71
92
|
|
|
72
93
|
/**
|
|
73
94
|
* Bounded observed-signal vocabulary surfaced to owner control planes. Mirrors
|
|
@@ -146,3 +167,4 @@ export interface AgentWireFrameEnvelope<TType extends AgentWireFrameType = Agent
|
|
|
146
167
|
|
|
147
168
|
/** An `AgentSessionEvent` serialized into a versioned wire frame. */
|
|
148
169
|
export type AgentWireEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventPayload>;
|
|
170
|
+
export type AgentWireCompactEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventFramePayload>;
|