@gajae-code/coding-agent 0.8.2 → 0.9.0
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 +29 -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 +105 -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/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/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -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 +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +8 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -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/web/scrapers/youtube.d.ts +8 -0
- package/package.json +7 -7
- 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/update-cli.ts +85 -9
- package/src/cli.ts +26 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/config/settings-schema.ts +68 -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/session-state-sidecar.ts +208 -13
- package/src/gjc-runtime/state-writer.ts +15 -0
- package/src/gjc-runtime/team-runtime.ts +164 -15
- 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 +1593 -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 +14 -5
- 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/status-line.ts +167 -54
- 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 +66 -47
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +9 -1
- package/src/modes/rpc/rpc-mode.ts +29 -3
- 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 +1 -0
- package/src/modes/utils/ui-helpers.ts +17 -4
- package/src/notifications/config.ts +16 -4
- package/src/notifications/index.ts +78 -5
- package/src/notifications/telegram-daemon-cli.ts +2 -2
- package/src/notifications/telegram-daemon-control.ts +2 -2
- package/src/notifications/telegram-daemon.ts +85 -8
- package/src/notifications/threaded-render.ts +17 -1
- 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 +1 -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 +32 -22
- package/src/session/agent-session.ts +732 -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 +65 -0
- 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/web/scrapers/youtube.ts +24 -12
|
@@ -24,10 +24,20 @@ import type { ResolveToolDetails } from "../../tools/resolve";
|
|
|
24
24
|
import { interruptHint } from "../shared";
|
|
25
25
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
26
26
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
27
|
+
import { argsWithPartialJson } from "../utils/ui-helpers";
|
|
27
28
|
|
|
28
29
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
29
30
|
|
|
30
31
|
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
32
|
+
|
|
33
|
+
/** Test-only performance counters for advisory baseline tests. */
|
|
34
|
+
export const __eventControllerPerfCounters = {
|
|
35
|
+
messageUpdateContentVisits: 0,
|
|
36
|
+
reset(): void {
|
|
37
|
+
this.messageUpdateContentVisits = 0;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
31
41
|
const COMPLETION_NOTIFY_COMMAND_TIMEOUT_MS = 10_000;
|
|
32
42
|
|
|
33
43
|
interface CompletionNotifyPayload {
|
|
@@ -100,6 +110,8 @@ export class EventController {
|
|
|
100
110
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
101
111
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
102
112
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
113
|
+
#toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
|
|
114
|
+
#thinkingContentIndices = new Set<number>();
|
|
103
115
|
#handlers: AgentSessionEventHandlers;
|
|
104
116
|
|
|
105
117
|
constructor(private ctx: InteractiveModeContext) {
|
|
@@ -307,12 +319,14 @@ export class EventController {
|
|
|
307
319
|
} else if (event.message.role === "assistant") {
|
|
308
320
|
this.#lastThinkingCount = 0;
|
|
309
321
|
this.#resetReadGroup();
|
|
322
|
+
this.#toolIntentCache.clear();
|
|
323
|
+
this.#thinkingContentIndices.clear();
|
|
310
324
|
this.ctx.streamingComponent = new AssistantMessageComponent(undefined, this.ctx.hideThinkingBlock, () =>
|
|
311
325
|
this.ctx.ui.requestRender(),
|
|
312
326
|
);
|
|
313
327
|
this.ctx.streamingMessage = event.message;
|
|
314
328
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
315
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
329
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
316
330
|
this.ctx.ui.requestRender();
|
|
317
331
|
}
|
|
318
332
|
}
|
|
@@ -375,46 +389,53 @@ export class EventController {
|
|
|
375
389
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
376
390
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
377
391
|
this.ctx.streamingMessage = event.message;
|
|
378
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
392
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
393
|
+
const contentIndex = event.assistantMessageEvent?.contentIndex;
|
|
394
|
+
const changedContent =
|
|
395
|
+
typeof contentIndex === "number" && contentIndex >= 0
|
|
396
|
+
? this.ctx.streamingMessage.content[contentIndex]
|
|
397
|
+
: undefined;
|
|
398
|
+
const contentsToProcess = changedContent ? [changedContent] : this.ctx.streamingMessage.content;
|
|
399
|
+
|
|
400
|
+
if (changedContent?.type === "thinking" && changedContent.thinking.trim()) {
|
|
401
|
+
if (typeof contentIndex === "number") this.#thinkingContentIndices.add(contentIndex);
|
|
402
|
+
const thinkingCount = this.#thinkingContentIndices.size;
|
|
403
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
404
|
+
this.#resetReadGroup();
|
|
405
|
+
this.#lastThinkingCount = thinkingCount;
|
|
406
|
+
}
|
|
407
|
+
} else if (!changedContent) {
|
|
408
|
+
const thinkingCount = this.ctx.streamingMessage.content.filter(
|
|
409
|
+
content => content.type === "thinking" && content.thinking.trim(),
|
|
410
|
+
).length;
|
|
411
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
412
|
+
this.#resetReadGroup();
|
|
413
|
+
this.#lastThinkingCount = thinkingCount;
|
|
414
|
+
}
|
|
386
415
|
}
|
|
387
416
|
|
|
388
|
-
for (const content of
|
|
417
|
+
for (const content of contentsToProcess) {
|
|
418
|
+
__eventControllerPerfCounters.messageUpdateContentVisits += 1;
|
|
389
419
|
if (content.type !== "toolCall") continue;
|
|
390
420
|
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
|
-
}
|
|
421
|
+
if (!readArgsHaveTarget(content.arguments)) continue;
|
|
397
422
|
if (!readArgsTargetInternalUrl(content.arguments)) {
|
|
398
423
|
this.#trackReadToolCall(content.id, content.arguments);
|
|
399
424
|
const component = this.ctx.pendingTools.get(content.id);
|
|
400
|
-
if (component)
|
|
401
|
-
|
|
402
|
-
} else {
|
|
425
|
+
if (component) component.updateArgs(content.arguments, content.id);
|
|
426
|
+
else {
|
|
403
427
|
const group = this.#getReadGroup();
|
|
404
428
|
group.updateArgs(content.arguments, content.id);
|
|
405
429
|
this.ctx.pendingTools.set(content.id, group);
|
|
406
430
|
}
|
|
407
431
|
continue;
|
|
408
432
|
}
|
|
409
|
-
// Internal URL read falls through to ToolExecutionComponent below.
|
|
410
433
|
}
|
|
411
434
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
? { ...content.arguments, __partialJson: content.partialJson }
|
|
417
|
-
: content.arguments;
|
|
435
|
+
const renderArgs = argsWithPartialJson(
|
|
436
|
+
content.arguments,
|
|
437
|
+
"partialJson" in content ? content.partialJson : undefined,
|
|
438
|
+
);
|
|
418
439
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
419
440
|
this.#resetReadGroup();
|
|
420
441
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
@@ -437,32 +458,30 @@ export class EventController {
|
|
|
437
458
|
this.ctx.chatContainer.addChild(component);
|
|
438
459
|
this.ctx.pendingTools.set(content.id, component);
|
|
439
460
|
} else {
|
|
440
|
-
|
|
441
|
-
if (component) {
|
|
442
|
-
component.updateArgs(renderArgs, content.id);
|
|
443
|
-
}
|
|
461
|
+
this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
|
|
444
462
|
}
|
|
445
|
-
}
|
|
446
463
|
|
|
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
464
|
const args = content.arguments;
|
|
451
465
|
if (!args || typeof args !== "object") continue;
|
|
466
|
+
let intent: string | undefined;
|
|
452
467
|
if (INTENT_FIELD in args) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
468
|
+
intent = String(args[INTENT_FIELD]);
|
|
469
|
+
} else {
|
|
470
|
+
const cached = this.#toolIntentCache.get(content.id);
|
|
471
|
+
if (cached?.args === args) intent = cached.intent;
|
|
472
|
+
else {
|
|
473
|
+
const tool = this.ctx.session.getToolByName(content.name);
|
|
474
|
+
if (typeof tool?.intent === "function") {
|
|
475
|
+
try {
|
|
476
|
+
intent = tool.intent(args as never)?.trim();
|
|
477
|
+
} catch {
|
|
478
|
+
// intent function must never break the UI
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
this.#toolIntentCache.set(content.id, { args, intent });
|
|
462
482
|
}
|
|
463
|
-
} catch {
|
|
464
|
-
// intent function must never break the UI
|
|
465
483
|
}
|
|
484
|
+
if (intent) this.#updateWorkingMessageFromIntent(intent);
|
|
466
485
|
}
|
|
467
486
|
|
|
468
487
|
this.ctx.ui.requestRender();
|
|
@@ -495,9 +514,9 @@ export class EventController {
|
|
|
495
514
|
// display only — does NOT mutate the persisted message's stopReason
|
|
496
515
|
// (the marker on errorMessage drives replay-side suppression).
|
|
497
516
|
const msgWithoutAbort = { ...this.ctx.streamingMessage, stopReason: "stop" as const };
|
|
498
|
-
this.ctx.streamingComponent.updateContent(msgWithoutAbort);
|
|
517
|
+
this.ctx.streamingComponent.updateContent(msgWithoutAbort, { streaming: false });
|
|
499
518
|
} else {
|
|
500
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
519
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: false });
|
|
501
520
|
}
|
|
502
521
|
|
|
503
522
|
if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
|
|
@@ -1308,7 +1308,7 @@ export class InputController {
|
|
|
1308
1308
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
|
1309
1309
|
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
1310
1310
|
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
|
1311
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
1311
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
1312
1312
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
1313
1313
|
}
|
|
1314
1314
|
|
|
@@ -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
|
|
|
@@ -1715,6 +1716,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1715
1716
|
planContent,
|
|
1716
1717
|
finalPlanFilePath: options.finalPlanFilePath,
|
|
1717
1718
|
contextPreserved: options.preserveContext === true,
|
|
1719
|
+
tools: this.session.getActiveToolNames(),
|
|
1718
1720
|
});
|
|
1719
1721
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
1720
1722
|
}
|
|
@@ -2206,6 +2208,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2206
2208
|
message => renderWorkingMessage(message, this.#getWorkingMessageAccent()),
|
|
2207
2209
|
this.#defaultWorkingMessage,
|
|
2208
2210
|
getSymbolTheme().spinnerFrames,
|
|
2211
|
+
{ timeDependentColor: true },
|
|
2209
2212
|
);
|
|
2210
2213
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
2211
2214
|
}
|
|
@@ -2363,6 +2366,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2363
2366
|
return this.#commandController.handleClearCommand();
|
|
2364
2367
|
}
|
|
2365
2368
|
|
|
2369
|
+
handleContextClearCommand(): Promise<void> {
|
|
2370
|
+
this.#prepareSessionSwitch();
|
|
2371
|
+
return this.#commandController.handleContextClearCommand();
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2366
2374
|
handleDropCommand(): Promise<void> {
|
|
2367
2375
|
this.#prepareSessionSwitch();
|
|
2368
2376
|
return this.#commandController.handleDropCommand();
|
|
@@ -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";
|
|
@@ -297,6 +302,7 @@ export async function runRpcMode(
|
|
|
297
302
|
output({ type: "ready" });
|
|
298
303
|
}
|
|
299
304
|
const emitRpcTitles = shouldEmitRpcTitles();
|
|
305
|
+
const rpcCapabilities = { compactMessageUpdate: false };
|
|
300
306
|
const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
301
307
|
|
|
302
308
|
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
@@ -636,8 +642,13 @@ export async function runRpcMode(
|
|
|
636
642
|
// Output all agent events as canonical agent-wire `event` frames (docs/rpc.md):
|
|
637
643
|
// { type:"event", protocol_version, session_id, seq, frame_id, payload:{ event_type, event } }.
|
|
638
644
|
const eventSequencer = new AgentWireFrameSequencer(session.sessionId);
|
|
645
|
+
const compactEventEncoder = new AgentWireCompactEventEncoder(eventSequencer);
|
|
639
646
|
session.subscribe(event => {
|
|
640
|
-
output(
|
|
647
|
+
output(
|
|
648
|
+
rpcCapabilities.compactMessageUpdate
|
|
649
|
+
? toAgentWireCompactEventFrame(event, compactEventEncoder)
|
|
650
|
+
: toAgentWireEventFrame(event, eventSequencer),
|
|
651
|
+
);
|
|
641
652
|
});
|
|
642
653
|
|
|
643
654
|
// Handle a single command through the shared agent-wire dispatcher so RPC
|
|
@@ -710,6 +721,21 @@ export async function runRpcMode(
|
|
|
710
721
|
hostUriBridge.handleResult(parsed);
|
|
711
722
|
return;
|
|
712
723
|
}
|
|
724
|
+
if ((parsed as RpcCommand).type === "set_capabilities") {
|
|
725
|
+
// RPC has no startup handshake; clients negotiate optional event-shape capabilities
|
|
726
|
+
// with this explicit command. Defaults remain legacy/full-frame until accepted here.
|
|
727
|
+
const command = parsed as Extract<RpcCommand, { type: "set_capabilities" }>;
|
|
728
|
+
const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
|
|
729
|
+
const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
|
|
730
|
+
rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
|
|
731
|
+
output(
|
|
732
|
+
rpcSuccess(command.id, "set_capabilities", {
|
|
733
|
+
acceptedCapabilities,
|
|
734
|
+
unsupported: requested.filter(capability => capability !== "compact_message_update"),
|
|
735
|
+
}),
|
|
736
|
+
);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
713
739
|
// Ordered commands run through a serial chain to preserve causal order; the
|
|
714
740
|
// reader never blocks, so cancellation commands stay responsive even while a
|
|
715
741
|
// long command is in flight (issue 13).
|
|
@@ -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>;
|
|
@@ -12,6 +12,9 @@ import { randomUUID } from "node:crypto";
|
|
|
12
12
|
import type { AgentSessionEvent } from "../../../session/agent-session";
|
|
13
13
|
import {
|
|
14
14
|
AGENT_WIRE_PROTOCOL_VERSION,
|
|
15
|
+
type AgentWireCompactAssistantMessageEvent,
|
|
16
|
+
type AgentWireCompactEventFrame,
|
|
17
|
+
type AgentWireCompactMessageUpdatePayload,
|
|
15
18
|
type AgentWireEventFrame,
|
|
16
19
|
type AgentWireEventPayload,
|
|
17
20
|
type AgentWireEventType,
|
|
@@ -19,6 +22,26 @@ import {
|
|
|
19
22
|
type AgentWireFrameType,
|
|
20
23
|
} from "./event-contract";
|
|
21
24
|
|
|
25
|
+
export const AGENT_WIRE_COMPACT_MESSAGE_UPDATE_CHECKPOINT_INTERVAL = 32;
|
|
26
|
+
|
|
27
|
+
function recordObject(value: unknown): Record<string, unknown> | undefined {
|
|
28
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function messageIdOf(message: unknown): string | null {
|
|
32
|
+
const id = recordObject(message)?.id;
|
|
33
|
+
return typeof id === "string" ? id : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function contentIndexOf(assistantMessageEvent: unknown): number | null {
|
|
37
|
+
const contentIndex = recordObject(assistantMessageEvent)?.contentIndex;
|
|
38
|
+
return typeof contentIndex === "number" && Number.isInteger(contentIndex) ? contentIndex : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function snapshotMessage<T>(message: T): T {
|
|
42
|
+
return structuredClone(message);
|
|
43
|
+
}
|
|
44
|
+
|
|
22
45
|
function assertNever(value: never): never {
|
|
23
46
|
throw new Error(`Unhandled AgentSessionEvent variant: ${JSON.stringify(value)}`);
|
|
24
47
|
}
|
|
@@ -123,6 +146,65 @@ export function toAgentWireEventFrame(
|
|
|
123
146
|
export function toAgentWireEventPayload(event: AgentSessionEvent): AgentWireEventPayload {
|
|
124
147
|
return { event_type: agentSessionEventType(event), event };
|
|
125
148
|
}
|
|
149
|
+
export function sanitizeAssistantMessageEventForCompact(
|
|
150
|
+
assistantMessageEvent: Extract<AgentSessionEvent, { type: "message_update" }>["assistantMessageEvent"],
|
|
151
|
+
): AgentWireCompactAssistantMessageEvent {
|
|
152
|
+
const {
|
|
153
|
+
partial: _partial,
|
|
154
|
+
message: _message,
|
|
155
|
+
error: _error,
|
|
156
|
+
...deltaOnly
|
|
157
|
+
} = assistantMessageEvent as Record<string, unknown>;
|
|
158
|
+
return deltaOnly as AgentWireCompactAssistantMessageEvent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface AgentWireCompactMessageUpdateOptions {
|
|
162
|
+
checkpointInterval?: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Stateful compact event serializer for opt-in RPC clients.
|
|
167
|
+
*
|
|
168
|
+
* Only `message_update` changes shape: it carries the immutable provider delta
|
|
169
|
+
* plus message/content identifiers, with periodic full-message checkpoints that
|
|
170
|
+
* are cloned synchronously before the caller returns to the event loop. All other
|
|
171
|
+
* event variants remain canonical full event frames so legacy bridge/RPC behavior
|
|
172
|
+
* and terminal `message_end` reconstruction are unchanged.
|
|
173
|
+
*/
|
|
174
|
+
export class AgentWireCompactEventEncoder {
|
|
175
|
+
readonly #sequencer: AgentWireFrameSequencer;
|
|
176
|
+
readonly #checkpointInterval: number;
|
|
177
|
+
#messageUpdateCount = 0;
|
|
178
|
+
|
|
179
|
+
constructor(sequencer: AgentWireFrameSequencer, options: AgentWireCompactMessageUpdateOptions = {}) {
|
|
180
|
+
this.#sequencer = sequencer;
|
|
181
|
+
this.#checkpointInterval = options.checkpointInterval ?? AGENT_WIRE_COMPACT_MESSAGE_UPDATE_CHECKPOINT_INTERVAL;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
frame(event: AgentSessionEvent): AgentWireCompactEventFrame {
|
|
185
|
+
if (event.type !== "message_update") return toAgentWireEventFrame(event, this.#sequencer);
|
|
186
|
+
this.#messageUpdateCount += 1;
|
|
187
|
+
const payload: AgentWireCompactMessageUpdatePayload = {
|
|
188
|
+
event_type: "message_update",
|
|
189
|
+
compact: true,
|
|
190
|
+
message_id: messageIdOf(event.message),
|
|
191
|
+
content_index: contentIndexOf(event.assistantMessageEvent),
|
|
192
|
+
assistantMessageEvent: sanitizeAssistantMessageEventForCompact(event.assistantMessageEvent),
|
|
193
|
+
};
|
|
194
|
+
if (this.#checkpointInterval > 0 && this.#messageUpdateCount % this.#checkpointInterval === 0) {
|
|
195
|
+
payload.checkpoint_message = snapshotMessage(event.message);
|
|
196
|
+
payload.checkpoint_reason = "periodic";
|
|
197
|
+
}
|
|
198
|
+
return this.#sequencer.next("event", payload);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function toAgentWireCompactEventFrame(
|
|
203
|
+
event: AgentSessionEvent,
|
|
204
|
+
encoder: AgentWireCompactEventEncoder,
|
|
205
|
+
): AgentWireCompactEventFrame {
|
|
206
|
+
return encoder.frame(event);
|
|
207
|
+
}
|
|
126
208
|
|
|
127
209
|
/** Back-compat alias for {@link toAgentWireEventFrame}. */
|
|
128
210
|
export const toBridgeEventFrame = toAgentWireEventFrame;
|
|
@@ -47,6 +47,7 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
|
|
|
47
47
|
set_host_tools: "host_tools",
|
|
48
48
|
set_host_uri_schemes: "host_uri",
|
|
49
49
|
get_pending_workflow_gates: "message:read",
|
|
50
|
+
set_capabilities: "control",
|
|
50
51
|
set_model: "model",
|
|
51
52
|
cycle_model: "model",
|
|
52
53
|
get_available_models: "model",
|
package/src/modes/types.ts
CHANGED
|
@@ -216,6 +216,7 @@ export interface InteractiveModeContext {
|
|
|
216
216
|
handleDumpCommand(): void;
|
|
217
217
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
218
218
|
handleClearCommand(): Promise<void>;
|
|
219
|
+
handleContextClearCommand(): Promise<void>;
|
|
219
220
|
handleDropCommand(): Promise<void>;
|
|
220
221
|
handleForkCommand(): Promise<void>;
|
|
221
222
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -36,6 +36,19 @@ interface RenderInitialMessagesOptions {
|
|
|
36
36
|
preserveExistingChat?: boolean;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function argsWithPartialJson(args: unknown, partialJson: unknown): unknown {
|
|
40
|
+
if (typeof partialJson !== "string" || !args || typeof args !== "object" || Array.isArray(args)) return args;
|
|
41
|
+
// Non-enumerable so the transient streaming buffer reaches renderers via direct
|
|
42
|
+
// property read but never serializes into persisted/wire message content.
|
|
43
|
+
Object.defineProperty(args, "__partialJson", {
|
|
44
|
+
value: partialJson,
|
|
45
|
+
enumerable: false,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
});
|
|
49
|
+
return args;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
type QueuedMessages = {
|
|
40
53
|
steering: string[];
|
|
41
54
|
followUp: string[];
|
|
@@ -392,10 +405,10 @@ export class UiHelpers {
|
|
|
392
405
|
|
|
393
406
|
readGroup = null;
|
|
394
407
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
395
|
-
const renderArgs =
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
408
|
+
const renderArgs = argsWithPartialJson(
|
|
409
|
+
content.arguments,
|
|
410
|
+
"partialJson" in content ? content.partialJson : undefined,
|
|
411
|
+
);
|
|
399
412
|
const component = new ToolExecutionComponent(
|
|
400
413
|
content.name,
|
|
401
414
|
renderArgs,
|
|
@@ -38,13 +38,24 @@ export function getNotificationConfig(settings: Settings): NotificationConfig {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function hasNonBlankValue(value: string | undefined): boolean {
|
|
42
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Is Telegram configured with usable non-blank boundary credentials? */
|
|
46
|
+
export function isTelegramConfigured(
|
|
47
|
+
cfg: NotificationConfig,
|
|
48
|
+
): cfg is NotificationConfig & { botToken: string; chatId: string } {
|
|
49
|
+
return cfg.enabled && hasNonBlankValue(cfg.botToken) && hasNonBlankValue(cfg.chatId);
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
/** Is global config sufficient for auto-on (enabled + at least one configured adapter)? */
|
|
42
53
|
export function isGloballyConfigured(cfg: NotificationConfig): boolean {
|
|
43
54
|
return (
|
|
44
55
|
cfg.enabled &&
|
|
45
|
-
((
|
|
46
|
-
(
|
|
47
|
-
(
|
|
56
|
+
(isTelegramConfigured(cfg) ||
|
|
57
|
+
(hasNonBlankValue(cfg.discord.botToken) && hasNonBlankValue(cfg.discord.channelId)) ||
|
|
58
|
+
(hasNonBlankValue(cfg.slack.botToken) && hasNonBlankValue(cfg.slack.channelId)))
|
|
48
59
|
);
|
|
49
60
|
}
|
|
50
61
|
|
|
@@ -88,6 +99,7 @@ export function isSessionNotificationsEnabled(input: {
|
|
|
88
99
|
/** Mask a bot token for display: first 4 chars + "…" + "(len N)"; "(unset)" when undefined/empty. Never reveal full token. */
|
|
89
100
|
export function maskToken(token: string | undefined): string {
|
|
90
101
|
if (!token) return "(unset)";
|
|
102
|
+
if (token.length <= 4) return `…(len ${token.length})`;
|
|
91
103
|
return `${token.slice(0, 4)}…(len ${token.length})`;
|
|
92
104
|
}
|
|
93
105
|
|
|
@@ -131,6 +143,6 @@ export function buildRedactedAction(
|
|
|
131
143
|
// Asks stay fully readable/answerable even under redaction.
|
|
132
144
|
if (action.kind === "ask") return action;
|
|
133
145
|
|
|
134
|
-
const { summary: _summary, question: _question, ...base } = action;
|
|
146
|
+
const { summary: _summary, question: _question, options: _options, ...base } = action;
|
|
135
147
|
return base;
|
|
136
148
|
}
|