@sayknow-cli/coding-agent 0.2.7 → 0.3.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 +20 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +25 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +136 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +69 -0
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +295 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +77 -0
- package/dist/types/notifications/threaded-render.d.ts +71 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +41 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/state-writer.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +14 -0
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +19 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +22 -0
- package/dist/types/web/search/provider.d.ts +18 -1
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/types.d.ts +12 -4
- package/package.json +10 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +132 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/args.ts +1 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +421 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -3
- package/src/commands/daemon.ts +47 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +143 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/skc/rules/ponytail.md +68 -0
- package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/ultragoal/SKILL.md +16 -0
- package/src/edit/modes/replace.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/hashline/hash.ts +1 -1
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +30 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/components/settings-selector.ts +7 -2
- package/src/modes/controllers/event-controller.ts +35 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +52 -4
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/theme/theme.ts +4 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +128 -0
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +842 -0
- package/src/notifications/managed-daemon.ts +163 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1591 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +136 -0
- package/src/notifications/threaded-render.ts +173 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/rlm/index.ts +19 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +195 -54
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skc-runtime/deep-interview-recorder.ts +2 -2
- package/src/skc-runtime/launch-tmux.ts +27 -5
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- package/src/skc-runtime/state-runtime.ts +18 -10
- package/src/skc-runtime/state-writer.ts +8 -8
- package/src/skc-runtime/tmux-common.ts +8 -0
- package/src/skc-runtime/tmux-sessions.ts +8 -1
- package/src/skc-runtime/ultragoal-guard.ts +57 -2
- package/src/skc-runtime/ultragoal-runtime.ts +105 -19
- package/src/skc-runtime/workflow-manifest.generated.json +56 -2
- package/src/skc-runtime/workflow-manifest.ts +18 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +77 -6
- package/src/tools/fetch.ts +78 -1
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +22 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/src/tools/telegram-send.ts +137 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +155 -0
- package/src/web/search/provider.ts +77 -18
- package/src/web/search/providers/anthropic.ts +70 -3
- package/src/web/search/providers/codex.ts +1 -119
- package/src/web/search/providers/gemini.ts +99 -0
- package/src/web/search/providers/insane.ts +551 -0
- package/src/web/search/providers/openai-compatible.ts +66 -32
- package/src/web/search/providers/text-citations.ts +111 -0
- package/src/web/search/types.ts +13 -2
- package/vendor/insane-search/LICENSE +21 -0
- package/vendor/insane-search/MANIFEST.json +24 -0
- package/vendor/insane-search/engine/__init__.py +23 -0
- package/vendor/insane-search/engine/__main__.py +128 -0
- package/vendor/insane-search/engine/bias_check.py +183 -0
- package/vendor/insane-search/engine/executor.py +254 -0
- package/vendor/insane-search/engine/fetch_chain.py +725 -0
- package/vendor/insane-search/engine/learning.py +175 -0
- package/vendor/insane-search/engine/phase0.py +214 -0
- package/vendor/insane-search/engine/safety.py +91 -0
- package/vendor/insane-search/engine/templates/package.json +11 -0
- package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
- package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
- package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
- package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
- package/vendor/insane-search/engine/tests/test_u1.py +200 -0
- package/vendor/insane-search/engine/tests/test_u4.py +131 -0
- package/vendor/insane-search/engine/tests/test_u5.py +163 -0
- package/vendor/insane-search/engine/tests/test_u7.py +124 -0
- package/vendor/insane-search/engine/transport.py +211 -0
- package/vendor/insane-search/engine/url_transforms.py +98 -0
- package/vendor/insane-search/engine/validators.py +331 -0
- package/vendor/insane-search/engine/waf_detector.py +214 -0
- package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session forum-topic registry for the threaded session surface.
|
|
3
|
+
*
|
|
4
|
+
* Each SKC session owns exactly one Telegram forum topic in the paired private
|
|
5
|
+
* DM. The topic is created once (via `createForumTopic`) and REUSED on resume,
|
|
6
|
+
* keyed by session id, so a resumed session streams back into its existing
|
|
7
|
+
* thread/history. The registry also tracks whether the one-time identity header
|
|
8
|
+
* has already been pinned, so it is sent exactly once per topic even across
|
|
9
|
+
* reconnects.
|
|
10
|
+
*
|
|
11
|
+
* State is a plain serialisable map persisted beside the daemon state files;
|
|
12
|
+
* topic creation is injected so this module is pure and unit-testable without a
|
|
13
|
+
* live Bot API.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Persisted record for one session's topic. */
|
|
17
|
+
export interface TopicRecord {
|
|
18
|
+
/** Telegram forum topic id (message_thread_id). */
|
|
19
|
+
topicId: string;
|
|
20
|
+
/** Whether the one-time identity header has been sent/pinned. */
|
|
21
|
+
identitySent: boolean;
|
|
22
|
+
/** Creation timestamp (ms epoch). */
|
|
23
|
+
createdAt: number;
|
|
24
|
+
/** Last applied topic title (for rename detection). */
|
|
25
|
+
name?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Serialisable shape persisted to disk. */
|
|
29
|
+
export interface TopicRegistryState {
|
|
30
|
+
/** sessionId -> record. */
|
|
31
|
+
topics: Record<string, TopicRecord>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function emptyTopicRegistryState(): TopicRegistryState {
|
|
35
|
+
return { topics: {} };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* In-memory registry over a serialisable state. Topic creation is injected via
|
|
40
|
+
* `getOrCreateTopic`'s `create` callback (the daemon supplies a real
|
|
41
|
+
* `createForumTopic` call); reuse-on-resume is automatic when a record exists.
|
|
42
|
+
*/
|
|
43
|
+
export class TopicRegistry {
|
|
44
|
+
private readonly topics: Map<string, TopicRecord>;
|
|
45
|
+
/** Maps topicId -> sessionId for fast inbound routing. */
|
|
46
|
+
private readonly byTopic = new Map<string, string>();
|
|
47
|
+
/** In-flight create promises, keyed by session, to dedupe concurrent creates. */
|
|
48
|
+
private readonly inflight = new Map<string, Promise<TopicRecord>>();
|
|
49
|
+
|
|
50
|
+
constructor(state: TopicRegistryState = emptyTopicRegistryState()) {
|
|
51
|
+
this.topics = new Map(Object.entries(state.topics ?? {}));
|
|
52
|
+
for (const [sessionId, record] of this.topics) this.byTopic.set(record.topicId, sessionId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Merge a serialized state into this registry, preserving all persisted fields. */
|
|
56
|
+
load(state: TopicRegistryState): void {
|
|
57
|
+
for (const [sessionId, record] of Object.entries(state.topics ?? {})) {
|
|
58
|
+
this.topics.set(sessionId, record);
|
|
59
|
+
this.byTopic.set(record.topicId, sessionId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolve the owning session for a topic id (for fail-closed inbound routing). */
|
|
64
|
+
sessionForTopic(topicId: string): string | undefined {
|
|
65
|
+
return this.byTopic.get(topicId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The existing topic record for a session, if any. */
|
|
69
|
+
get(sessionId: string): TopicRecord | undefined {
|
|
70
|
+
return this.topics.get(sessionId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Return the existing topic for `sessionId`, or create one via `create`
|
|
75
|
+
* (called only on first use). Reuse-on-resume: an existing record is
|
|
76
|
+
* returned without invoking `create`.
|
|
77
|
+
*/
|
|
78
|
+
async getOrCreateTopic(
|
|
79
|
+
sessionId: string,
|
|
80
|
+
create: () => Promise<string>,
|
|
81
|
+
now: () => number = Date.now,
|
|
82
|
+
): Promise<TopicRecord> {
|
|
83
|
+
const existing = this.topics.get(sessionId);
|
|
84
|
+
if (existing) return existing;
|
|
85
|
+
// Concurrency guard: many session frames (identity/idle/turn/ask) can race
|
|
86
|
+
// to first-use the same session. Without this, each call passes the
|
|
87
|
+
// `existing` check before `create()` resolves and creates a DUPLICATE
|
|
88
|
+
// forum topic. Share a single in-flight create per session id.
|
|
89
|
+
const pending = this.inflight.get(sessionId);
|
|
90
|
+
if (pending) return pending;
|
|
91
|
+
const promise = (async () => {
|
|
92
|
+
const topicId = await create();
|
|
93
|
+
const record: TopicRecord = { topicId, identitySent: false, createdAt: now() };
|
|
94
|
+
this.topics.set(sessionId, record);
|
|
95
|
+
this.byTopic.set(topicId, sessionId);
|
|
96
|
+
return record;
|
|
97
|
+
})();
|
|
98
|
+
this.inflight.set(sessionId, promise);
|
|
99
|
+
try {
|
|
100
|
+
return await promise;
|
|
101
|
+
} finally {
|
|
102
|
+
this.inflight.delete(sessionId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Mark the identity header as sent for a session. Idempotent. */
|
|
107
|
+
markIdentitySent(sessionId: string): void {
|
|
108
|
+
const record = this.topics.get(sessionId);
|
|
109
|
+
if (record) record.identitySent = true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Whether the identity header still needs sending for this session. */
|
|
113
|
+
needsIdentity(sessionId: string): boolean {
|
|
114
|
+
const record = this.topics.get(sessionId);
|
|
115
|
+
return record ? !record.identitySent : true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Record the topic's applied title. Returns `true` when it changed (so the
|
|
120
|
+
* caller should `editForumTopic`), `false` when already current or unknown.
|
|
121
|
+
*/
|
|
122
|
+
applyName(sessionId: string, name: string): boolean {
|
|
123
|
+
const record = this.topics.get(sessionId);
|
|
124
|
+
if (!record || record.name === name) return false;
|
|
125
|
+
record.name = name;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Serialise for atomic persistence beside the daemon state. */
|
|
130
|
+
serialize(): TopicRegistryState {
|
|
131
|
+
return { topics: Object.fromEntries(this.topics) };
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/rlm/index.ts
CHANGED
|
@@ -240,8 +240,27 @@ async function writeRlmMetadata(input: {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/**
|
|
244
|
+
* RLM artifacts are scoped under a SKC session directory and resolving their
|
|
245
|
+
* paths is a *write* (it must pick a concrete session). When `skc rlm` runs
|
|
246
|
+
* standalone — no parent agent, no `SKC_SESSION_ID` in the environment — there is
|
|
247
|
+
* no session to resolve and `resolveSkcSessionForWrite` throws
|
|
248
|
+
* `missing_for_write`. Establish a dedicated SKC session id in that case and pin
|
|
249
|
+
* it into the environment so artifact-path resolution, the per-session activity
|
|
250
|
+
* marker, and the child agent's workflow state all share one writable session.
|
|
251
|
+
*
|
|
252
|
+
* Returns the resolved (existing or freshly generated) SKC session id.
|
|
253
|
+
*/
|
|
254
|
+
export function ensureRlmSkcSessionId(): string {
|
|
255
|
+
const existing = resolveSessionIdFromSources({ envSessionId: process.env.SKC_SESSION_ID })?.skcSessionId;
|
|
256
|
+
if (existing) return existing;
|
|
257
|
+
const generated = `rlm-${generateRlmSessionId()}`;
|
|
258
|
+
process.env.SKC_SESSION_ID = generated;
|
|
259
|
+
return generated;
|
|
260
|
+
}
|
|
243
261
|
export async function runRlmCommand(argv: string[]): Promise<void> {
|
|
244
262
|
const cwd = getProjectDir();
|
|
263
|
+
ensureRlmSkcSessionId();
|
|
245
264
|
const { dataPath, resumeSessionId, minSuccessfulRuns, rest } = extractRlmFlags(argv);
|
|
246
265
|
const dataContext = await loadRlmDataContext(cwd, dataPath);
|
|
247
266
|
|
package/src/sdk.ts
CHANGED
|
@@ -81,6 +81,12 @@ import type { HindsightSessionState } from "./hindsight/state";
|
|
|
81
81
|
import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
|
|
82
82
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
83
83
|
import { resolveMemoryBackend } from "./memory-backend";
|
|
84
|
+
import { createNotificationsExtension } from "./notifications";
|
|
85
|
+
import {
|
|
86
|
+
getNotificationConfig,
|
|
87
|
+
type NotificationConfig,
|
|
88
|
+
shouldRegisterNotificationsExtension,
|
|
89
|
+
} from "./notifications/config";
|
|
84
90
|
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
85
91
|
import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
86
92
|
import { MCPManager } from "./runtime-mcp";
|
|
@@ -1240,6 +1246,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1240
1246
|
getPlanModeState: () => session?.getPlanModeState(),
|
|
1241
1247
|
getGoalModeState: () => session?.getGoalModeState(),
|
|
1242
1248
|
getWorkflowGateEmitter: () => session?.getWorkflowGateEmitter(),
|
|
1249
|
+
getAskAnswerSource: () => session?.getAskAnswerSource(),
|
|
1243
1250
|
getGoalRuntime: () => session?.goalRuntime,
|
|
1244
1251
|
getClientBridge: () => session?.clientBridge,
|
|
1245
1252
|
getCompactContext: () => session.formatCompactContext(),
|
|
@@ -1388,6 +1395,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1388
1395
|
if (customTools.length > 0) {
|
|
1389
1396
|
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1390
1397
|
}
|
|
1398
|
+
let notificationCfg: NotificationConfig | undefined;
|
|
1399
|
+
try {
|
|
1400
|
+
notificationCfg = getNotificationConfig(Settings.instance);
|
|
1401
|
+
} catch {
|
|
1402
|
+
notificationCfg = undefined;
|
|
1403
|
+
}
|
|
1404
|
+
if (shouldRegisterNotificationsExtension({ env: process.env, cfg: notificationCfg })) {
|
|
1405
|
+
inlineExtensions.push(createNotificationsExtension);
|
|
1406
|
+
}
|
|
1391
1407
|
|
|
1392
1408
|
// Extension/module discovery is quarantined; retain only the private
|
|
1393
1409
|
// runtime needed for bundled product extensions, explicitly supplied SDK
|
|
@@ -238,8 +238,9 @@ import {
|
|
|
238
238
|
type DiscoverableTool,
|
|
239
239
|
type DiscoverableToolSearchIndex,
|
|
240
240
|
} from "../tool-discovery/tool-index";
|
|
241
|
-
import type { ToolSession } from "../tools";
|
|
241
|
+
import type { AskAnswerSource, ToolSession } from "../tools";
|
|
242
242
|
import { AskTool } from "../tools/ask";
|
|
243
|
+
import { getAskAnswerSource as getAskAnswerSourceFromRegistry } from "../tools/ask-answer-registry";
|
|
243
244
|
import { assertEditableFile } from "../tools/auto-generated-guard";
|
|
244
245
|
import { releaseTabsForOwner } from "../tools/browser/tab-supervisor";
|
|
245
246
|
import type { CheckpointState } from "../tools/checkpoint";
|
|
@@ -285,6 +286,8 @@ import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
|
285
286
|
import { YieldQueue } from "./yield-queue";
|
|
286
287
|
|
|
287
288
|
/** Session-specific events that extend the core AgentEvent */
|
|
289
|
+
export type AutoCompactionContinuationSkipReason = "auto_continue_disabled_non_resumable_tail";
|
|
290
|
+
|
|
288
291
|
export type AgentSessionEvent =
|
|
289
292
|
| AgentEvent
|
|
290
293
|
| { type: "auto_compaction_start"; reason: "threshold" | "overflow" | "idle"; action: "context-full" | "handoff" }
|
|
@@ -297,6 +300,7 @@ export type AgentSessionEvent =
|
|
|
297
300
|
errorMessage?: string;
|
|
298
301
|
/** True when compaction was skipped for a benign reason (no model, no candidates, nothing to compact). */
|
|
299
302
|
skipped?: boolean;
|
|
303
|
+
continuationSkipReason?: AutoCompactionContinuationSkipReason;
|
|
300
304
|
}
|
|
301
305
|
| {
|
|
302
306
|
type: "auto_retry_start";
|
|
@@ -313,6 +317,7 @@ export type AgentSessionEvent =
|
|
|
313
317
|
| { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
|
|
314
318
|
| { type: "todo_auto_clear" }
|
|
315
319
|
| { type: "irc_message"; message: CustomMessage }
|
|
320
|
+
| { type: "subagent_steer_message"; message: CustomMessage }
|
|
316
321
|
| { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string }
|
|
317
322
|
| { type: "thinking_level_changed"; thinkingLevel: ThinkingLevel | undefined }
|
|
318
323
|
| { type: "goal_updated"; goal: Goal | null; state?: GoalModeState };
|
|
@@ -1867,7 +1872,6 @@ export class AgentSession {
|
|
|
1867
1872
|
this.#resolveTtsrResume();
|
|
1868
1873
|
return;
|
|
1869
1874
|
}
|
|
1870
|
-
this.#ttsrAbortPending = false;
|
|
1871
1875
|
this.#perToolTtsrInjections.clear();
|
|
1872
1876
|
const ttsrSettings = this.#ttsrManager?.getSettings();
|
|
1873
1877
|
if (ttsrSettings?.contextMode === "discard" && targetAssistantIndex !== -1) {
|
|
@@ -1896,11 +1900,22 @@ export class AgentSession {
|
|
|
1896
1900
|
);
|
|
1897
1901
|
this.#markTtsrInjected(details.rules);
|
|
1898
1902
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1903
|
+
await this.#scheduleAgentContinue({
|
|
1904
|
+
delayMs: 0,
|
|
1905
|
+
generation,
|
|
1906
|
+
shouldContinue: () => {
|
|
1907
|
+
this.#ttsrAbortPending = false;
|
|
1908
|
+
return true;
|
|
1909
|
+
},
|
|
1910
|
+
onSkip: () => {
|
|
1911
|
+
this.#ttsrAbortPending = false;
|
|
1912
|
+
this.#resolveTtsrResume();
|
|
1913
|
+
},
|
|
1914
|
+
onError: () => {
|
|
1915
|
+
this.#ttsrAbortPending = false;
|
|
1916
|
+
this.#resolveTtsrResume();
|
|
1917
|
+
},
|
|
1918
|
+
});
|
|
1904
1919
|
},
|
|
1905
1920
|
{ delayMs: 50 },
|
|
1906
1921
|
);
|
|
@@ -2212,7 +2227,7 @@ export class AgentSession {
|
|
|
2212
2227
|
#schedulePostPromptTask(
|
|
2213
2228
|
task: (signal: AbortSignal) => Promise<void>,
|
|
2214
2229
|
options?: { delayMs?: number; generation?: number; onSkip?: () => void },
|
|
2215
|
-
): void {
|
|
2230
|
+
): Promise<void> {
|
|
2216
2231
|
const delayMs = options?.delayMs ?? 0;
|
|
2217
2232
|
const signal = this.#postPromptTasksAbortController.signal;
|
|
2218
2233
|
const scheduled = (async () => {
|
|
@@ -2234,18 +2249,20 @@ export class AgentSession {
|
|
|
2234
2249
|
await task(signal);
|
|
2235
2250
|
})();
|
|
2236
2251
|
this.#trackPostPromptTask(scheduled);
|
|
2252
|
+
return scheduled;
|
|
2237
2253
|
}
|
|
2238
2254
|
|
|
2239
2255
|
#scheduleAgentContinue(options?: {
|
|
2240
2256
|
delayMs?: number;
|
|
2241
2257
|
generation?: number;
|
|
2258
|
+
skipCompactionCheck?: boolean;
|
|
2242
2259
|
shouldContinue?: () => boolean;
|
|
2243
2260
|
onSkip?: (reason: "generation_changed" | "aborted_signal" | "queue_drained") => void;
|
|
2244
2261
|
onError?: (error: unknown) => void;
|
|
2245
|
-
}): void {
|
|
2262
|
+
}): Promise<void> {
|
|
2246
2263
|
const scheduledGeneration = options?.generation;
|
|
2247
2264
|
const signal = this.#postPromptTasksAbortController.signal;
|
|
2248
|
-
this.#schedulePostPromptTask(
|
|
2265
|
+
return this.#schedulePostPromptTask(
|
|
2249
2266
|
async () => {
|
|
2250
2267
|
if (signal.aborted) {
|
|
2251
2268
|
options?.onSkip?.("aborted_signal");
|
|
@@ -2261,6 +2278,9 @@ export class AgentSession {
|
|
|
2261
2278
|
}
|
|
2262
2279
|
try {
|
|
2263
2280
|
await this.#maybeRestoreRetryFallbackPrimary();
|
|
2281
|
+
if (!options?.skipCompactionCheck) {
|
|
2282
|
+
await this.#checkEstimatedContextBeforePrompt();
|
|
2283
|
+
}
|
|
2264
2284
|
await this.agent.continue();
|
|
2265
2285
|
} catch (error) {
|
|
2266
2286
|
logger.warn("agent.continue failed after scheduling", {
|
|
@@ -2305,6 +2325,34 @@ export class AgentSession {
|
|
|
2305
2325
|
}
|
|
2306
2326
|
}
|
|
2307
2327
|
|
|
2328
|
+
#detectOverflowRetryContinuationSkip(): AutoCompactionContinuationSkipReason | undefined {
|
|
2329
|
+
this.#stripOverflowFailedTurnForRetry();
|
|
2330
|
+
if (this.#isResumableAgentTail()) return undefined;
|
|
2331
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
2332
|
+
return compactionSettings.autoContinue === false ? "auto_continue_disabled_non_resumable_tail" : undefined;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
#scheduleOverflowRetryContinuation(generation: number): void {
|
|
2336
|
+
this.#stripOverflowFailedTurnForRetry();
|
|
2337
|
+
if (this.#isResumableAgentTail()) {
|
|
2338
|
+
this.#scheduleAgentContinue({
|
|
2339
|
+
delayMs: 100,
|
|
2340
|
+
generation,
|
|
2341
|
+
onSkip: reason => this.#logCompactionContinuationSkipped("overflow_retry", reason),
|
|
2342
|
+
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
2343
|
+
});
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
2348
|
+
if (compactionSettings.autoContinue !== false) {
|
|
2349
|
+
this.#scheduleAutoContinuePrompt(generation);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
this.#logCompactionContinuationSkipped("overflow_retry", "auto_continue_disabled_non_resumable_tail");
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2308
2356
|
#scheduleAutoContinuePrompt(generation: number): void {
|
|
2309
2357
|
const continuePrompt = async () => {
|
|
2310
2358
|
await this.#promptWithMessage(
|
|
@@ -3050,6 +3098,7 @@ export class AgentSession {
|
|
|
3050
3098
|
willRetry: event.willRetry,
|
|
3051
3099
|
errorMessage: event.errorMessage,
|
|
3052
3100
|
skipped: event.skipped,
|
|
3101
|
+
continuationSkipReason: event.continuationSkipReason,
|
|
3053
3102
|
});
|
|
3054
3103
|
} else if (event.type === "auto_retry_start") {
|
|
3055
3104
|
await this.#extensionRunner.emit({
|
|
@@ -4385,6 +4434,10 @@ export class AgentSession {
|
|
|
4385
4434
|
return this.#workflowGateEmitter;
|
|
4386
4435
|
}
|
|
4387
4436
|
|
|
4437
|
+
getAskAnswerSource(): AskAnswerSource | undefined {
|
|
4438
|
+
return getAskAnswerSourceFromRegistry(this.sessionId);
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4388
4441
|
setWorkflowGateEmitter(emitter: WorkflowGateEmitter | undefined): void {
|
|
4389
4442
|
this.#workflowGateEmitter = emitter;
|
|
4390
4443
|
if (emitter) {
|
|
@@ -5453,6 +5506,16 @@ export class AgentSession {
|
|
|
5453
5506
|
return;
|
|
5454
5507
|
}
|
|
5455
5508
|
|
|
5509
|
+
// No explicit delivery mode: only a live stream makes prompt() throw
|
|
5510
|
+
// AgentBusyError, so queue the message as steering while streaming.
|
|
5511
|
+
// Compaction is intentionally NOT diverted here: prompt() handles an
|
|
5512
|
+
// in-flight compaction internally, and #queueSteer would otherwise park
|
|
5513
|
+
// the message in the steering queue with no turn to consume it.
|
|
5514
|
+
if (this.isStreaming) {
|
|
5515
|
+
await this.#queueSteer(text, images);
|
|
5516
|
+
return;
|
|
5517
|
+
}
|
|
5518
|
+
|
|
5456
5519
|
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
|
5457
5520
|
await this.prompt(text, {
|
|
5458
5521
|
expandPromptTemplates: false,
|
|
@@ -5856,12 +5919,46 @@ export class AgentSession {
|
|
|
5856
5919
|
return this.#activeModelProfile;
|
|
5857
5920
|
}
|
|
5858
5921
|
|
|
5922
|
+
/**
|
|
5923
|
+
* The model selector ("provider/id") that resume restores as the session
|
|
5924
|
+
* default — the latest session-log `model_change` with role="default".
|
|
5925
|
+
* Model-profile activation snapshots this before mutating the session so a
|
|
5926
|
+
* failed-activation rollback can restore the pre-activation resume default
|
|
5927
|
+
* instead of promoting a transient runtime model to the resume default.
|
|
5928
|
+
*/
|
|
5929
|
+
getSessionDefaultModelSelector(): string | undefined {
|
|
5930
|
+
return this.sessionManager.buildSessionContext().models.default;
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
/**
|
|
5934
|
+
* Re-assert the session resume default ("provider/id") in the session log
|
|
5935
|
+
* WITHOUT touching the live runtime model. Appends a `model_change` with
|
|
5936
|
+
* role="default"; never writes to global settings (apply-for-this-session
|
|
5937
|
+
* semantics). Used by model-profile activation rollback to neutralize the
|
|
5938
|
+
* profile main model the failed activation already recorded as the default.
|
|
5939
|
+
*/
|
|
5940
|
+
recordResumeDefaultModel(selector: string): void {
|
|
5941
|
+
this.sessionManager.appendModelChange(selector, "default");
|
|
5942
|
+
}
|
|
5943
|
+
|
|
5859
5944
|
/**
|
|
5860
5945
|
* Set model temporarily (for this session only).
|
|
5861
5946
|
* Validates API key, saves to session log but NOT to settings.
|
|
5947
|
+
*
|
|
5948
|
+
* The change is recorded in the session log as `role: "temporary"` by
|
|
5949
|
+
* default, which means it is NOT restored as the session default on resume —
|
|
5950
|
+
* transient retry/fallback/context-promotion/plan switches must not clobber
|
|
5951
|
+
* the user's explicit pick (issue #849). Model-profile activation passes
|
|
5952
|
+
* `persistAsSessionDefault: true` so the profile's main model becomes the
|
|
5953
|
+
* session default and survives resume, while still not being written to
|
|
5954
|
+
* global settings (new sessions keep the global default).
|
|
5862
5955
|
* @throws Error if no API key available for the model
|
|
5863
5956
|
*/
|
|
5864
|
-
async setModelTemporary(
|
|
5957
|
+
async setModelTemporary(
|
|
5958
|
+
model: Model,
|
|
5959
|
+
thinkingLevel?: ThinkingLevel,
|
|
5960
|
+
options?: { persistAsSessionDefault?: boolean },
|
|
5961
|
+
): Promise<void> {
|
|
5865
5962
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
5866
5963
|
const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
|
|
5867
5964
|
if (!apiKey) {
|
|
@@ -5870,7 +5967,10 @@ export class AgentSession {
|
|
|
5870
5967
|
|
|
5871
5968
|
this.#clearActiveRetryFallback();
|
|
5872
5969
|
this.#setModelWithProviderSessionReset(model);
|
|
5873
|
-
this.sessionManager.appendModelChange(
|
|
5970
|
+
this.sessionManager.appendModelChange(
|
|
5971
|
+
`${model.provider}/${model.id}`,
|
|
5972
|
+
options?.persistAsSessionDefault ? "default" : "temporary",
|
|
5973
|
+
);
|
|
5874
5974
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
5875
5975
|
|
|
5876
5976
|
// Apply explicit thinking level if given; otherwise prefer the model's
|
|
@@ -6669,6 +6769,8 @@ export class AgentSession {
|
|
|
6669
6769
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
6670
6770
|
if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
|
|
6671
6771
|
await this.#runAutoCompaction("overflow", true);
|
|
6772
|
+
} else {
|
|
6773
|
+
this.#scheduleOverflowRetryContinuation(generation);
|
|
6672
6774
|
}
|
|
6673
6775
|
return;
|
|
6674
6776
|
}
|
|
@@ -6679,17 +6781,19 @@ export class AgentSession {
|
|
|
6679
6781
|
// Skip if this was an error (non-overflow errors don't have usage data)
|
|
6680
6782
|
if (assistantMessage.stopReason === "error") return;
|
|
6681
6783
|
let contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
6682
|
-
|
|
6784
|
+
// Model maxTokens is a capability ceiling, not a per-turn reservation.
|
|
6785
|
+
// Auto maintenance should track actual context fullness.
|
|
6786
|
+
const autoCompactionOutputReserveTokens = 0;
|
|
6683
6787
|
// Cache-epoch invariant: pruning rewrites already-sent toolResult history,
|
|
6684
6788
|
// which breaks the provider prompt-cache prefix mid-epoch. Only prune at a
|
|
6685
6789
|
// sanctioned maintenance boundary, i.e. when the un-pruned context already
|
|
6686
6790
|
// crosses the compaction threshold. Pruning may then avert full compaction.
|
|
6687
|
-
if (!shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6791
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) return;
|
|
6688
6792
|
const pruneResult = await this.#pruneToolOutputs();
|
|
6689
6793
|
if (pruneResult) {
|
|
6690
6794
|
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
6691
6795
|
}
|
|
6692
|
-
if (shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6796
|
+
if (shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
|
|
6693
6797
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
6694
6798
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
6695
6799
|
if (!promoted) {
|
|
@@ -6767,14 +6871,16 @@ export class AgentSession {
|
|
|
6767
6871
|
if (!compactionSettings.enabled || compactionSettings.strategy === "off") return;
|
|
6768
6872
|
|
|
6769
6873
|
let contextTokens = this.#estimateContextTokensForCompaction(pendingMessages).tokens;
|
|
6770
|
-
|
|
6771
|
-
|
|
6874
|
+
// Model maxTokens is a capability ceiling, not a per-turn reservation.
|
|
6875
|
+
// Auto maintenance should track actual context fullness.
|
|
6876
|
+
const autoCompactionOutputReserveTokens = 0;
|
|
6877
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) return;
|
|
6772
6878
|
|
|
6773
6879
|
const pruneResult = await this.#pruneToolOutputs();
|
|
6774
6880
|
if (pruneResult) {
|
|
6775
6881
|
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
6776
6882
|
}
|
|
6777
|
-
if (shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6883
|
+
if (shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
|
|
6778
6884
|
await this.#runAutoCompaction("threshold", false, false, {
|
|
6779
6885
|
continueAfterMaintenance: false,
|
|
6780
6886
|
deferHandoffMaintenance: false,
|
|
@@ -7677,32 +7783,18 @@ export class AgentSession {
|
|
|
7677
7783
|
|
|
7678
7784
|
const preparation = prepareCompaction(pathEntries, compactionSettings);
|
|
7679
7785
|
if (!preparation) {
|
|
7786
|
+
const continuationSkipReason = willRetry ? this.#detectOverflowRetryContinuationSkip() : undefined;
|
|
7680
7787
|
await this.#emitSessionEvent({
|
|
7681
7788
|
type: "auto_compaction_end",
|
|
7682
7789
|
action,
|
|
7683
7790
|
result: undefined,
|
|
7684
7791
|
aborted: false,
|
|
7685
|
-
willRetry:
|
|
7792
|
+
willRetry: willRetry && !continuationSkipReason,
|
|
7686
7793
|
skipped: true,
|
|
7794
|
+
continuationSkipReason,
|
|
7687
7795
|
});
|
|
7688
7796
|
if (willRetry) {
|
|
7689
|
-
this.#
|
|
7690
|
-
if (this.#isResumableAgentTail()) {
|
|
7691
|
-
this.#scheduleAgentContinue({
|
|
7692
|
-
delayMs: 100,
|
|
7693
|
-
generation,
|
|
7694
|
-
onSkip: skipReason => this.#logCompactionContinuationSkipped("overflow_retry", skipReason),
|
|
7695
|
-
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
7696
|
-
});
|
|
7697
|
-
} else {
|
|
7698
|
-
const tail = this.agent.state.messages.at(-1) as AssistantMessage | undefined;
|
|
7699
|
-
logger.warn("Auto-compaction continuation skipped", {
|
|
7700
|
-
source: "overflow_retry",
|
|
7701
|
-
reason: "not_resumable_tail",
|
|
7702
|
-
role: tail?.role,
|
|
7703
|
-
stopReason: tail?.stopReason,
|
|
7704
|
-
});
|
|
7705
|
-
}
|
|
7797
|
+
this.#scheduleOverflowRetryContinuation(generation);
|
|
7706
7798
|
} else if (continueAfterMaintenance && reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7707
7799
|
this.#scheduleAgentContinue({
|
|
7708
7800
|
delayMs: 100,
|
|
@@ -7913,26 +8005,18 @@ export class AgentSession {
|
|
|
7913
8005
|
details,
|
|
7914
8006
|
preserveData,
|
|
7915
8007
|
};
|
|
7916
|
-
|
|
8008
|
+
const continuationSkipReason = willRetry ? this.#detectOverflowRetryContinuationSkip() : undefined;
|
|
8009
|
+
await this.#emitSessionEvent({
|
|
8010
|
+
type: "auto_compaction_end",
|
|
8011
|
+
action,
|
|
8012
|
+
result,
|
|
8013
|
+
aborted: false,
|
|
8014
|
+
willRetry: willRetry && !continuationSkipReason,
|
|
8015
|
+
continuationSkipReason,
|
|
8016
|
+
});
|
|
7917
8017
|
|
|
7918
8018
|
if (willRetry) {
|
|
7919
|
-
this.#
|
|
7920
|
-
if (!this.#isResumableAgentTail()) {
|
|
7921
|
-
const tail = this.agent.state.messages.at(-1) as AssistantMessage | undefined;
|
|
7922
|
-
logger.warn("Auto-compaction continuation skipped", {
|
|
7923
|
-
source: "overflow_retry",
|
|
7924
|
-
reason: "not_resumable_tail",
|
|
7925
|
-
role: tail?.role,
|
|
7926
|
-
stopReason: tail?.stopReason,
|
|
7927
|
-
});
|
|
7928
|
-
} else {
|
|
7929
|
-
this.#scheduleAgentContinue({
|
|
7930
|
-
delayMs: 100,
|
|
7931
|
-
generation,
|
|
7932
|
-
onSkip: reason => this.#logCompactionContinuationSkipped("overflow_retry", reason),
|
|
7933
|
-
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
7934
|
-
});
|
|
7935
|
-
}
|
|
8019
|
+
this.#scheduleOverflowRetryContinuation(generation);
|
|
7936
8020
|
} else if (continueAfterMaintenance && reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7937
8021
|
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
7938
8022
|
// Kick the loop so queued messages are actually delivered.
|
|
@@ -9089,6 +9173,63 @@ export class AgentSession {
|
|
|
9089
9173
|
void this.#emitSessionEvent({ type: "irc_message", message: record });
|
|
9090
9174
|
}
|
|
9091
9175
|
|
|
9176
|
+
emitSubagentSteerObservation(args: { from: string; to: string; body: string; timestamp?: number }): void {
|
|
9177
|
+
const timestamp = args.timestamp ?? Date.now();
|
|
9178
|
+
const observationId = crypto.randomUUID();
|
|
9179
|
+
const message: CustomMessage = {
|
|
9180
|
+
role: "custom",
|
|
9181
|
+
customType: "subagent:steer",
|
|
9182
|
+
content: `[Steer \`${args.from}\` ⇨ \`${args.to}\` (queued)]\n\n${args.body}`,
|
|
9183
|
+
display: true,
|
|
9184
|
+
details: { observationId, from: args.from, to: args.to, body: args.body, state: "queued" },
|
|
9185
|
+
attribution: "agent",
|
|
9186
|
+
timestamp,
|
|
9187
|
+
};
|
|
9188
|
+
void this.#emitSessionEvent({ type: "subagent_steer_message", message });
|
|
9189
|
+
this.#forwardSubagentSteerRelayToMain({
|
|
9190
|
+
from: args.from,
|
|
9191
|
+
to: args.to,
|
|
9192
|
+
body: args.body,
|
|
9193
|
+
observationId,
|
|
9194
|
+
timestamp,
|
|
9195
|
+
});
|
|
9196
|
+
}
|
|
9197
|
+
|
|
9198
|
+
#forwardSubagentSteerRelayToMain(args: {
|
|
9199
|
+
from: string;
|
|
9200
|
+
to: string;
|
|
9201
|
+
body: string;
|
|
9202
|
+
observationId: string;
|
|
9203
|
+
timestamp: number;
|
|
9204
|
+
}): void {
|
|
9205
|
+
const registry = this.#agentRegistry;
|
|
9206
|
+
if (!registry) return;
|
|
9207
|
+
if (this.#agentId === MAIN_AGENT_ID) return;
|
|
9208
|
+
const mainRef = registry.get(MAIN_AGENT_ID);
|
|
9209
|
+
const mainSession = mainRef?.session;
|
|
9210
|
+
if (!mainSession || mainSession === this) return;
|
|
9211
|
+
const record: CustomMessage = {
|
|
9212
|
+
role: "custom",
|
|
9213
|
+
customType: "subagent:steer:relay",
|
|
9214
|
+
content: `[Steer \`${args.from}\` ⇨ \`${args.to}\` (queued)]\n\n${args.body}`,
|
|
9215
|
+
display: true,
|
|
9216
|
+
details: {
|
|
9217
|
+
observationId: args.observationId,
|
|
9218
|
+
from: args.from,
|
|
9219
|
+
to: args.to,
|
|
9220
|
+
body: args.body,
|
|
9221
|
+
state: "queued",
|
|
9222
|
+
},
|
|
9223
|
+
attribution: "agent",
|
|
9224
|
+
timestamp: args.timestamp,
|
|
9225
|
+
};
|
|
9226
|
+
mainSession.emitSubagentSteerRelayObservation(record);
|
|
9227
|
+
}
|
|
9228
|
+
|
|
9229
|
+
emitSubagentSteerRelayObservation(record: CustomMessage): void {
|
|
9230
|
+
void this.#emitSessionEvent({ type: "subagent_steer_message", message: record });
|
|
9231
|
+
}
|
|
9232
|
+
|
|
9092
9233
|
/**
|
|
9093
9234
|
* Run a single ephemeral side-channel turn against this session's current
|
|
9094
9235
|
* model + system prompt + history. No tools are used; the side request
|