@gajae-code/coding-agent 0.11.6 → 0.11.8
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 +92 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/settings-schema.d.ts +70 -7
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/main.d.ts +32 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +4 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +26 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +30 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
- package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +25 -4
- package/dist/types/session/session-storage.d.ts +18 -0
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +2 -92
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +52 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +44 -11
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +18 -0
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +73 -7
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +75 -3
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +93 -15
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/internal-urls/local-protocol.ts +64 -21
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +111 -37
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +73 -11
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +25 -4
- package/src/modes/components/welcome.ts +22 -18
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +38 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +149 -63
- package/src/modes/interactive-mode.ts +57 -34
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +4 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/bus/chat-daemon-control.ts +19 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +244 -70
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +68 -20
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
- package/src/sdk/bus/telegram-daemon-control.ts +147 -35
- package/src/sdk/bus/telegram-daemon.ts +2463 -450
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +58 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +31 -5
- package/src/sdk/startup-capability.ts +9 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +292 -154
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +1163 -137
- package/src/session/internal/managed-session-storage.ts +397 -234
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +620 -403
- package/src/session/session-storage.ts +128 -11
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -89
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +13 -131
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +4 -0
- package/src/tools/read.ts +2 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
|
@@ -105,6 +105,16 @@ import {
|
|
|
105
105
|
type FallbackAttemptToken,
|
|
106
106
|
type FallbackTriggerClass,
|
|
107
107
|
} from "@gajae-code/ai/utils/fallback-transport";
|
|
108
|
+
import {
|
|
109
|
+
BTW_MAX_ANSWER_UTF8_BYTES,
|
|
110
|
+
BTW_MAX_QUESTION_UTF8_BYTES,
|
|
111
|
+
BTW_STREAM_IDLE_TIMEOUT_MS,
|
|
112
|
+
BTW_STREAM_TOTAL_TIMEOUT_MS,
|
|
113
|
+
type BtwTextExchange,
|
|
114
|
+
boundBtwExchanges,
|
|
115
|
+
truncateUtf8,
|
|
116
|
+
utf8ByteLength,
|
|
117
|
+
} from "./btw-contract";
|
|
108
118
|
|
|
109
119
|
export interface ForkContextSeedMetadata {
|
|
110
120
|
sourceSessionId: string;
|
|
@@ -235,6 +245,7 @@ import { loadActiveSubskillTools } from "../extensibility/gjc-plugins/tools";
|
|
|
235
245
|
import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
236
246
|
import { buildSkillPromptMessage, type Skill, type SkillWarning } from "../extensibility/skills";
|
|
237
247
|
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
248
|
+
import { assertDeepInterviewIntentManifest } from "../gjc-runtime/deep-interview-state";
|
|
238
249
|
import { buildGjcRuntimeSessionEnv, consumePendingGoalModeRequest } from "../gjc-runtime/goal-mode-request";
|
|
239
250
|
import {
|
|
240
251
|
assertNonEmptyGjcSessionId,
|
|
@@ -242,6 +253,7 @@ import {
|
|
|
242
253
|
sessionStateDir,
|
|
243
254
|
} from "../gjc-runtime/session-layout";
|
|
244
255
|
import {
|
|
256
|
+
ownerTerminalContextFromEnvironment,
|
|
245
257
|
persistCoordinatorRuntimeStateFromEvent,
|
|
246
258
|
registerCoordinatorRuntimeStateFinalizer,
|
|
247
259
|
} from "../gjc-runtime/session-state-sidecar";
|
|
@@ -250,7 +262,7 @@ import { GoalRuntime } from "../goals/runtime";
|
|
|
250
262
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
251
263
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
252
264
|
import { buildSkillStopOutput, ensureWorkflowSkillActivationState } from "../hooks/skill-state";
|
|
253
|
-
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
265
|
+
import { initializeLocalRoot, type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
254
266
|
import { shutdownAll as shutdownAllLspClients } from "../lsp/client";
|
|
255
267
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
256
268
|
import {
|
|
@@ -827,7 +839,6 @@ function isLocalModelEndpoint(model: Model | undefined): boolean {
|
|
|
827
839
|
}
|
|
828
840
|
|
|
829
841
|
const IRC_REPLY_MAX_BYTES = 4096;
|
|
830
|
-
const BTW_ESTABLISHMENT_TIMEOUT_MS = 15_000;
|
|
831
842
|
|
|
832
843
|
export type EphemeralTurnPurpose = "btw" | "background";
|
|
833
844
|
|
|
@@ -837,8 +848,34 @@ interface EphemeralTurnBaseArgs {
|
|
|
837
848
|
signal?: AbortSignal;
|
|
838
849
|
}
|
|
839
850
|
|
|
851
|
+
export interface BtwRoleTextMessage {
|
|
852
|
+
role: "user" | "assistant";
|
|
853
|
+
text: string;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export interface BtwConversationScope {
|
|
857
|
+
model: Model;
|
|
858
|
+
systemPrompt: string[];
|
|
859
|
+
messages: BtwRoleTextMessage[];
|
|
860
|
+
thinkingLevel: ThinkingLevel;
|
|
861
|
+
hideThinkingSummary: boolean;
|
|
862
|
+
serviceTier: ServiceTier | undefined;
|
|
863
|
+
credentialSessionId: string;
|
|
864
|
+
providerAffinitySessionId: string;
|
|
865
|
+
sideSessionId: string;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export interface BtwTurnCapture {
|
|
869
|
+
question: string;
|
|
870
|
+
scope: BtwConversationScope | undefined;
|
|
871
|
+
}
|
|
872
|
+
|
|
840
873
|
export type EphemeralTurnArgs =
|
|
841
|
-
| (EphemeralTurnBaseArgs & {
|
|
874
|
+
| (Omit<EphemeralTurnBaseArgs, "promptText"> & {
|
|
875
|
+
purpose: "btw";
|
|
876
|
+
turn: BtwTurnCapture;
|
|
877
|
+
contextExchanges?: readonly BtwTextExchange[];
|
|
878
|
+
})
|
|
842
879
|
| (EphemeralTurnBaseArgs & {
|
|
843
880
|
purpose?: "background";
|
|
844
881
|
/** Internal caller-supplied, non-persistent context such as the IRC roster. */
|
|
@@ -1633,6 +1670,7 @@ export class AgentSession {
|
|
|
1633
1670
|
|
|
1634
1671
|
#skillsSettings: SkillsSettings | undefined;
|
|
1635
1672
|
#activeSkillState: { skill: string; sessionId?: string } | undefined;
|
|
1673
|
+
#restoredWorkflowSkillState: { skill: string; sessionId: string } | undefined;
|
|
1636
1674
|
|
|
1637
1675
|
// Model registry for API key resolution
|
|
1638
1676
|
#modelRegistry: ModelRegistry;
|
|
@@ -2452,6 +2490,34 @@ export class AgentSession {
|
|
|
2452
2490
|
return undefined;
|
|
2453
2491
|
}
|
|
2454
2492
|
}
|
|
2493
|
+
/** Provider-facing ask metadata must expose only the active deep-interview phase. */
|
|
2494
|
+
getDeepInterviewAskStage(): "topology" | "post-topology" | undefined {
|
|
2495
|
+
const currentSessionId = this.sessionManager.getSessionId();
|
|
2496
|
+
const inMemory = this.#activeSkillState;
|
|
2497
|
+
const active =
|
|
2498
|
+
inMemory && (!inMemory.sessionId || inMemory.sessionId === currentSessionId)
|
|
2499
|
+
? inMemory
|
|
2500
|
+
: this.#restoredWorkflowSkillState?.sessionId === currentSessionId
|
|
2501
|
+
? this.#restoredWorkflowSkillState
|
|
2502
|
+
: undefined;
|
|
2503
|
+
if (active?.skill !== "deep-interview") return undefined;
|
|
2504
|
+
try {
|
|
2505
|
+
assertNonEmptyGjcSessionId(currentSessionId, "AgentSession.getDeepInterviewAskStage");
|
|
2506
|
+
const stateDir = sessionStateDir(this.sessionManager.getCwd(), currentSessionId);
|
|
2507
|
+
const filePath = path.join(
|
|
2508
|
+
stateDir,
|
|
2509
|
+
path.basename(sessionModeStatePath(this.sessionManager.getCwd(), currentSessionId, active.skill)),
|
|
2510
|
+
);
|
|
2511
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
2512
|
+
const parsed = JSON.parse(raw) as { state?: { intent_contract?: unknown } };
|
|
2513
|
+
const intentContract = parsed.state?.intent_contract;
|
|
2514
|
+
if (intentContract === undefined) return "topology";
|
|
2515
|
+
assertDeepInterviewIntentManifest(intentContract);
|
|
2516
|
+
return "post-topology";
|
|
2517
|
+
} catch {
|
|
2518
|
+
return undefined;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2455
2521
|
|
|
2456
2522
|
/** Peek the in-flight directive's invocation handler for use by the resolve tool. */
|
|
2457
2523
|
peekQueueInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined {
|
|
@@ -4518,6 +4584,8 @@ export class AgentSession {
|
|
|
4518
4584
|
return {
|
|
4519
4585
|
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
4520
4586
|
isManagedDestination: () => this.sessionManager.isManagedDestination(),
|
|
4587
|
+
// Required for verified legacy local:// migration on managed sessions (#2797 / #2925).
|
|
4588
|
+
getManagedLegacyLocalMigrationSource: () => this.sessionManager.getManagedLegacyLocalMigrationSource(),
|
|
4521
4589
|
getSessionId: () => this.sessionManager.getSessionId(),
|
|
4522
4590
|
};
|
|
4523
4591
|
}
|
|
@@ -5092,7 +5160,7 @@ export class AgentSession {
|
|
|
5092
5160
|
// No-op when this session opened no tabs. Failure is logged, not thrown.
|
|
5093
5161
|
this.#unregisterResourceGc?.();
|
|
5094
5162
|
this.#unregisterResourceGc = undefined;
|
|
5095
|
-
this.#unregisterRuntimeStateFinalizer?.();
|
|
5163
|
+
if (ownerTerminalContextFromEnvironment() === null) this.#unregisterRuntimeStateFinalizer?.();
|
|
5096
5164
|
this.#unregisterRuntimeStateFinalizer = undefined;
|
|
5097
5165
|
await releaseTabsForOwner(this.sessionManager.getSessionId()).catch((error: unknown) =>
|
|
5098
5166
|
logger.warn("session dispose: releaseTabsForOwner failed", { error }),
|
|
@@ -6745,8 +6813,9 @@ export class AgentSession {
|
|
|
6745
6813
|
if (!activeSkill) {
|
|
6746
6814
|
try {
|
|
6747
6815
|
const activeState = await readVisibleSkillActiveState(this.sessionManager.getCwd(), sessionId);
|
|
6748
|
-
activeSkill =
|
|
6749
|
-
|
|
6816
|
+
activeSkill = activeState?.active_skills?.find(
|
|
6817
|
+
entry => entry.active !== false && entry.session_id === sessionId,
|
|
6818
|
+
)?.skill;
|
|
6750
6819
|
} catch (error) {
|
|
6751
6820
|
logger.warn("Failed to read durable workflow skill state while restoring ask tool", {
|
|
6752
6821
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -6758,7 +6827,12 @@ export class AgentSession {
|
|
|
6758
6827
|
// a predecessor session's workflow state to the current identity.
|
|
6759
6828
|
if (this.#isDisposed || this.sessionManager.getSessionId() !== sessionId) return;
|
|
6760
6829
|
}
|
|
6761
|
-
if (activeSkill && isCanonicalGjcWorkflowSkill(activeSkill.trim()))
|
|
6830
|
+
if (activeSkill && isCanonicalGjcWorkflowSkill(activeSkill.trim())) {
|
|
6831
|
+
if (!inMemoryActiveSkill) this.#restoredWorkflowSkillState = { skill: activeSkill.trim(), sessionId };
|
|
6832
|
+
this.#attachAskTool();
|
|
6833
|
+
} else if (this.#restoredWorkflowSkillState?.sessionId === sessionId) {
|
|
6834
|
+
this.#restoredWorkflowSkillState = undefined;
|
|
6835
|
+
}
|
|
6762
6836
|
}
|
|
6763
6837
|
|
|
6764
6838
|
#attachAskTool(): void {
|
|
@@ -7310,6 +7384,7 @@ export class AgentSession {
|
|
|
7310
7384
|
}
|
|
7311
7385
|
}
|
|
7312
7386
|
// In-memory tracking keeps `getActiveSkillState` accurate for the chain guard.
|
|
7387
|
+
this.#restoredWorkflowSkillState = undefined;
|
|
7313
7388
|
this.#activeSkillState = active ? { skill, sessionId } : undefined;
|
|
7314
7389
|
if (active) {
|
|
7315
7390
|
await this.refreshGjcSubskillTools();
|
|
@@ -13993,167 +14068,127 @@ export class AgentSession {
|
|
|
13993
14068
|
this.#ircRosterClaim = null;
|
|
13994
14069
|
}
|
|
13995
14070
|
|
|
14071
|
+
createBtwConversationScope(instruction: string): BtwConversationScope {
|
|
14072
|
+
const model = this.model;
|
|
14073
|
+
if (!model) throw new Error("No active model on session");
|
|
14074
|
+
const providerAffinitySessionId = this.agent.providerSessionId ?? this.agent.sessionId ?? this.sessionId;
|
|
14075
|
+
return {
|
|
14076
|
+
model,
|
|
14077
|
+
systemPrompt: [...this.systemPrompt, instruction],
|
|
14078
|
+
messages: this.#projectBtwVisibleText(this.buildDisplaySessionContext().messages),
|
|
14079
|
+
thinkingLevel: this.thinkingLevel ?? ThinkingLevel.Off,
|
|
14080
|
+
hideThinkingSummary: this.agent.hideThinkingSummary ?? false,
|
|
14081
|
+
serviceTier: this.serviceTier,
|
|
14082
|
+
credentialSessionId: providerAffinitySessionId,
|
|
14083
|
+
providerAffinitySessionId,
|
|
14084
|
+
sideSessionId: `${providerAffinitySessionId}:btw:${crypto.randomUUID()}`,
|
|
14085
|
+
};
|
|
14086
|
+
}
|
|
14087
|
+
|
|
14088
|
+
#projectBtwVisibleText(messages: readonly AgentMessage[]): BtwRoleTextMessage[] {
|
|
14089
|
+
const projected: BtwRoleTextMessage[] = [];
|
|
14090
|
+
for (const message of messages) {
|
|
14091
|
+
if (message.role !== "user" && message.role !== "assistant") continue;
|
|
14092
|
+
const text = (
|
|
14093
|
+
typeof message.content === "string"
|
|
14094
|
+
? message.content
|
|
14095
|
+
: message.content
|
|
14096
|
+
.filter((block): block is TextContent => block.type === "text")
|
|
14097
|
+
.map(block => block.text)
|
|
14098
|
+
.join("")
|
|
14099
|
+
).trim();
|
|
14100
|
+
if (!text) continue;
|
|
14101
|
+
projected.push({ role: message.role, text });
|
|
14102
|
+
}
|
|
14103
|
+
return projected;
|
|
14104
|
+
}
|
|
14105
|
+
|
|
14106
|
+
#buildBtwAssistantMessage(text: string): AssistantMessage {
|
|
14107
|
+
return {
|
|
14108
|
+
role: "assistant",
|
|
14109
|
+
content: [{ type: "text", text }],
|
|
14110
|
+
api: "btw",
|
|
14111
|
+
provider: "btw",
|
|
14112
|
+
model: "btw",
|
|
14113
|
+
usage: {
|
|
14114
|
+
input: 0,
|
|
14115
|
+
output: 0,
|
|
14116
|
+
cacheRead: 0,
|
|
14117
|
+
cacheWrite: 0,
|
|
14118
|
+
totalTokens: 0,
|
|
14119
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
14120
|
+
},
|
|
14121
|
+
stopReason: "stop",
|
|
14122
|
+
timestamp: 0,
|
|
14123
|
+
};
|
|
14124
|
+
}
|
|
13996
14125
|
/**
|
|
13997
|
-
* Run a single ephemeral side-channel turn
|
|
13998
|
-
*
|
|
13999
|
-
*
|
|
14000
|
-
*
|
|
14126
|
+
* Run a single ephemeral side-channel turn without modifying session history.
|
|
14127
|
+
* Background turns retain IRC/session behavior. `/btw` turns require a
|
|
14128
|
+
* pre-frozen, visible-text-only scope and bypass extension context transforms,
|
|
14129
|
+
* provider observability hooks, and session persistence surfaces.
|
|
14001
14130
|
*/
|
|
14002
14131
|
async runEphemeralTurn(args: EphemeralTurnArgs): Promise<EphemeralTurnResult> {
|
|
14003
14132
|
args.signal?.throwIfAborted();
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
const
|
|
14008
|
-
? this.#buildBtwEphemeralSnapshot(
|
|
14009
|
-
cloneJsonValueForForkSeed(this.buildDisplaySessionContext().messages),
|
|
14010
|
-
args.promptText,
|
|
14011
|
-
)
|
|
14012
|
-
: undefined;
|
|
14013
|
-
const model = this.model;
|
|
14014
|
-
const systemPrompt = isBtw ? [...this.systemPrompt] : undefined;
|
|
14015
|
-
const thinkingLevel = this.thinkingLevel;
|
|
14016
|
-
const hideThinkingSummary = this.agent.hideThinkingSummary;
|
|
14017
|
-
const serviceTier = this.serviceTier;
|
|
14018
|
-
const providerAffinitySessionId = this.agent.providerSessionId ?? this.agent.sessionId ?? this.sessionId;
|
|
14019
|
-
const sideSessionId = isBtw ? `${providerAffinitySessionId}:btw:${crypto.randomUUID()}` : undefined;
|
|
14020
|
-
const backgroundArgs = isBtw ? undefined : args;
|
|
14021
|
-
const callerOwnsRosterClaim = backgroundArgs?.ircRosterClaim !== undefined;
|
|
14022
|
-
const rosterClaim = isBtw
|
|
14023
|
-
? null
|
|
14024
|
-
: callerOwnsRosterClaim
|
|
14025
|
-
? backgroundArgs?.ircRosterClaim
|
|
14026
|
-
: this.#claimIrcRosterCandidate();
|
|
14133
|
+
if (args.purpose === "btw") return await this.#runBtwTurn(args);
|
|
14134
|
+
|
|
14135
|
+
const callerOwnsRosterClaim = args.ircRosterClaim !== undefined;
|
|
14136
|
+
const rosterClaim = callerOwnsRosterClaim ? args.ircRosterClaim : this.#claimIrcRosterCandidate();
|
|
14027
14137
|
const rosterClaimIsCurrent = () =>
|
|
14028
14138
|
!rosterClaim || this.#isCurrentIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
14029
|
-
const prependMessagesValid = () => rosterClaimIsCurrent() &&
|
|
14139
|
+
const prependMessagesValid = () => rosterClaimIsCurrent() && args.prependMessagesValid?.() !== false;
|
|
14030
14140
|
const rosterMessage = !callerOwnsRosterClaim && rosterClaimIsCurrent() ? rosterClaim?.message : undefined;
|
|
14031
14141
|
try {
|
|
14142
|
+
const model = this.model;
|
|
14032
14143
|
if (!model) throw new Error("No active model on session");
|
|
14033
|
-
const
|
|
14034
|
-
const apiKey = await awaitEphemeralAbort(
|
|
14035
|
-
this.#modelRegistry.getApiKey(model, credentialSessionId),
|
|
14036
|
-
args.signal,
|
|
14037
|
-
);
|
|
14144
|
+
const apiKey = await awaitEphemeralAbort(this.#modelRegistry.getApiKey(model, this.sessionId), args.signal);
|
|
14038
14145
|
if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
14039
|
-
|
|
14040
14146
|
const prependMessages = prependMessagesValid()
|
|
14041
|
-
? [...(rosterMessage ? [rosterMessage] : []), ...(
|
|
14147
|
+
? [...(rosterMessage ? [rosterMessage] : []), ...(args.prependMessages ?? [])]
|
|
14042
14148
|
: undefined;
|
|
14043
|
-
let snapshot =
|
|
14149
|
+
let snapshot = this.#buildEphemeralSnapshot(args.promptText, prependMessages);
|
|
14044
14150
|
let llmMessages = await awaitEphemeralAbort(this.convertMessagesToLlm(snapshot, args.signal), args.signal);
|
|
14045
|
-
if (
|
|
14151
|
+
if (prependMessages && !prependMessagesValid()) {
|
|
14046
14152
|
snapshot = this.#buildEphemeralSnapshot(args.promptText);
|
|
14047
14153
|
llmMessages = await awaitEphemeralAbort(this.convertMessagesToLlm(snapshot, args.signal), args.signal);
|
|
14048
14154
|
}
|
|
14049
|
-
const context: Context = {
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14155
|
+
const context: Context = { systemPrompt: this.systemPrompt, messages: llmMessages, tools: [] };
|
|
14156
|
+
const ephemeralSessionId = crypto.randomUUID();
|
|
14157
|
+
const options = this.prepareSimpleStreamOptions(
|
|
14158
|
+
{
|
|
14159
|
+
apiKey,
|
|
14160
|
+
sessionId: ephemeralSessionId,
|
|
14161
|
+
metadata: buildSessionMetadata(
|
|
14162
|
+
ephemeralSessionId,
|
|
14163
|
+
model.provider,
|
|
14164
|
+
this.#modelRegistry.authStorage,
|
|
14165
|
+
this.sessionId,
|
|
14166
|
+
),
|
|
14167
|
+
reasoning: toReasoningEffort(this.thinkingLevel),
|
|
14168
|
+
hideThinkingSummary: this.agent.hideThinkingSummary,
|
|
14169
|
+
serviceTier: this.serviceTier,
|
|
14170
|
+
signal: args.signal,
|
|
14171
|
+
toolChoice: "none",
|
|
14172
|
+
},
|
|
14173
|
+
model.provider,
|
|
14174
|
+
);
|
|
14175
|
+
args.signal?.throwIfAborted();
|
|
14055
14176
|
let replyText = "";
|
|
14056
14177
|
let assistantMessage: AssistantMessage | undefined;
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
)
|
|
14065
|
-
|
|
14066
|
-
let establishmentTimer: NodeJS.Timeout | undefined;
|
|
14067
|
-
try {
|
|
14068
|
-
requestSignal.throwIfAborted();
|
|
14069
|
-
const options: SimpleStreamOptions = {
|
|
14070
|
-
apiKey,
|
|
14071
|
-
sessionId: sideSessionId!,
|
|
14072
|
-
metadata: buildSessionMetadata(
|
|
14073
|
-
sideSessionId!,
|
|
14074
|
-
model.provider,
|
|
14075
|
-
this.#modelRegistry.authStorage,
|
|
14076
|
-
providerAffinitySessionId,
|
|
14077
|
-
),
|
|
14078
|
-
reasoning: toReasoningEffort(thinkingLevel),
|
|
14079
|
-
hideThinkingSummary,
|
|
14080
|
-
serviceTier,
|
|
14081
|
-
onPayload: this.#onPayload,
|
|
14082
|
-
signal: requestSignal,
|
|
14083
|
-
toolChoice: "none",
|
|
14084
|
-
requestMaxRetries: 0,
|
|
14085
|
-
streamMaxRetries: 0,
|
|
14086
|
-
streamFirstEventTimeoutMs: 0,
|
|
14087
|
-
};
|
|
14088
|
-
establishmentTimer = setTimeout(
|
|
14089
|
-
() => establishmentAbort.abort(timeoutError),
|
|
14090
|
-
BTW_ESTABLISHMENT_TIMEOUT_MS,
|
|
14091
|
-
);
|
|
14092
|
-
establishmentTimer.unref?.();
|
|
14093
|
-
const stream = streamSimple(model, context, options);
|
|
14094
|
-
let receivedProviderEvent = false;
|
|
14095
|
-
for await (const event of stream) {
|
|
14096
|
-
if (!receivedProviderEvent && event.type !== "start") {
|
|
14097
|
-
receivedProviderEvent = true;
|
|
14098
|
-
clearTimeout(establishmentTimer);
|
|
14099
|
-
establishmentTimer = undefined;
|
|
14100
|
-
}
|
|
14101
|
-
if (event.type === "text_delta") {
|
|
14102
|
-
replyText += event.delta;
|
|
14103
|
-
args.onTextDelta?.(event.delta);
|
|
14104
|
-
} else if (event.type === "done") {
|
|
14105
|
-
assistantMessage = event.message;
|
|
14106
|
-
break;
|
|
14107
|
-
} else if (event.type === "error") {
|
|
14108
|
-
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14109
|
-
}
|
|
14110
|
-
}
|
|
14111
|
-
requestSignal.throwIfAborted();
|
|
14112
|
-
} catch (error) {
|
|
14113
|
-
if (establishmentAbort.signal.aborted && !args.signal?.aborted) throw timeoutError;
|
|
14114
|
-
throw error;
|
|
14115
|
-
} finally {
|
|
14116
|
-
if (establishmentTimer) clearTimeout(establishmentTimer);
|
|
14117
|
-
}
|
|
14118
|
-
} else {
|
|
14119
|
-
const ephemeralSessionId = crypto.randomUUID();
|
|
14120
|
-
const options = this.prepareSimpleStreamOptions(
|
|
14121
|
-
{
|
|
14122
|
-
apiKey,
|
|
14123
|
-
sessionId: ephemeralSessionId,
|
|
14124
|
-
metadata: buildSessionMetadata(
|
|
14125
|
-
ephemeralSessionId,
|
|
14126
|
-
model.provider,
|
|
14127
|
-
this.#modelRegistry.authStorage,
|
|
14128
|
-
this.sessionId,
|
|
14129
|
-
),
|
|
14130
|
-
reasoning: toReasoningEffort(thinkingLevel),
|
|
14131
|
-
hideThinkingSummary,
|
|
14132
|
-
serviceTier,
|
|
14133
|
-
signal: args.signal,
|
|
14134
|
-
toolChoice: "none",
|
|
14135
|
-
},
|
|
14136
|
-
model.provider,
|
|
14137
|
-
);
|
|
14138
|
-
args.signal?.throwIfAborted();
|
|
14139
|
-
const stream = streamSimple(model, context, options);
|
|
14140
|
-
for await (const event of stream) {
|
|
14141
|
-
if (event.type === "text_delta") {
|
|
14142
|
-
replyText += event.delta;
|
|
14143
|
-
args.onTextDelta?.(event.delta);
|
|
14144
|
-
} else if (event.type === "done") {
|
|
14145
|
-
assistantMessage = event.message;
|
|
14146
|
-
break;
|
|
14147
|
-
} else if (event.type === "error") {
|
|
14148
|
-
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14149
|
-
}
|
|
14178
|
+
for await (const event of streamSimple(model, context, options)) {
|
|
14179
|
+
if (event.type === "text_delta") {
|
|
14180
|
+
replyText += event.delta;
|
|
14181
|
+
args.onTextDelta?.(event.delta);
|
|
14182
|
+
} else if (event.type === "done") {
|
|
14183
|
+
assistantMessage = event.message;
|
|
14184
|
+
break;
|
|
14185
|
+
} else if (event.type === "error") {
|
|
14186
|
+
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14150
14187
|
}
|
|
14151
14188
|
}
|
|
14152
|
-
|
|
14153
14189
|
if (!assistantMessage) throw new Error("Ephemeral turn ended without a final message");
|
|
14154
14190
|
args.signal?.throwIfAborted();
|
|
14155
14191
|
if (
|
|
14156
|
-
!isBtw &&
|
|
14157
14192
|
!callerOwnsRosterClaim &&
|
|
14158
14193
|
rosterClaim &&
|
|
14159
14194
|
assistantMessage.stopReason !== "error" &&
|
|
@@ -14161,21 +14196,117 @@ export class AgentSession {
|
|
|
14161
14196
|
) {
|
|
14162
14197
|
this.#commitIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
14163
14198
|
}
|
|
14164
|
-
args.signal?.throwIfAborted();
|
|
14165
14199
|
return { replyText: replyText.trim(), assistantMessage };
|
|
14166
14200
|
} finally {
|
|
14167
|
-
if (!
|
|
14168
|
-
|
|
14201
|
+
if (!callerOwnsRosterClaim && rosterClaim) this.#releaseIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
14202
|
+
}
|
|
14203
|
+
}
|
|
14204
|
+
|
|
14205
|
+
async #runBtwTurn(args: Extract<EphemeralTurnArgs, { purpose: "btw" }>): Promise<EphemeralTurnResult> {
|
|
14206
|
+
const model = args.turn.scope?.model;
|
|
14207
|
+
const credentialSessionId = args.turn.scope?.credentialSessionId;
|
|
14208
|
+
if (!model || !credentialSessionId) throw new Error("The /btw conversation scope was scrubbed.");
|
|
14209
|
+
if (utf8ByteLength(args.turn.question) > BTW_MAX_QUESTION_UTF8_BYTES) {
|
|
14210
|
+
throw new RangeError(`/btw questions are limited to ${BTW_MAX_QUESTION_UTF8_BYTES} UTF-8 bytes.`);
|
|
14211
|
+
}
|
|
14212
|
+
const apiKey = await awaitEphemeralAbort(this.#modelRegistry.getApiKey(model, credentialSessionId), args.signal);
|
|
14213
|
+
if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
14214
|
+
const scope = args.turn.scope;
|
|
14215
|
+
if (!scope) throw new Error("The /btw conversation scope was scrubbed.");
|
|
14216
|
+
const messages = scope.messages.map(message => ({
|
|
14217
|
+
role: message.role,
|
|
14218
|
+
content: [{ type: "text" as const, text: message.text }],
|
|
14219
|
+
})) as Message[];
|
|
14220
|
+
for (const exchange of boundBtwExchanges(args.contextExchanges ?? [])) {
|
|
14221
|
+
messages.push({
|
|
14222
|
+
role: "user",
|
|
14223
|
+
content: [{ type: "text", text: exchange.question }],
|
|
14224
|
+
} as Message);
|
|
14225
|
+
messages.push({
|
|
14226
|
+
role: "assistant",
|
|
14227
|
+
content: [{ type: "text", text: exchange.answer }],
|
|
14228
|
+
} as Message);
|
|
14229
|
+
}
|
|
14230
|
+
messages.push({
|
|
14231
|
+
role: "user",
|
|
14232
|
+
content: [{ type: "text", text: args.turn.question }],
|
|
14233
|
+
} as Message);
|
|
14234
|
+
const context: Context = { systemPrompt: scope.systemPrompt, messages, tools: [] };
|
|
14235
|
+
const timeoutAbort = new AbortController();
|
|
14236
|
+
const requestSignal = args.signal ? AbortSignal.any([args.signal, timeoutAbort.signal]) : timeoutAbort.signal;
|
|
14237
|
+
const options: SimpleStreamOptions = {
|
|
14238
|
+
apiKey,
|
|
14239
|
+
sessionId: scope.sideSessionId,
|
|
14240
|
+
reasoning: toReasoningEffort(scope.thinkingLevel),
|
|
14241
|
+
hideThinkingSummary: scope.hideThinkingSummary,
|
|
14242
|
+
serviceTier: scope.serviceTier,
|
|
14243
|
+
signal: requestSignal,
|
|
14244
|
+
toolChoice: "none",
|
|
14245
|
+
requestMaxRetries: 0,
|
|
14246
|
+
streamMaxRetries: 0,
|
|
14247
|
+
streamFirstEventTimeoutMs: 0,
|
|
14248
|
+
};
|
|
14249
|
+
const iterator = streamSimple(scope.model, context, options)[Symbol.asyncIterator]();
|
|
14250
|
+
let replyText = "";
|
|
14251
|
+
let completed = false;
|
|
14252
|
+
let active = true;
|
|
14253
|
+
let onTextDelta = args.onTextDelta;
|
|
14254
|
+
let idleTimer: NodeJS.Timeout | undefined;
|
|
14255
|
+
const timeout = (message: string) => {
|
|
14256
|
+
const error = new Error(message);
|
|
14257
|
+
error.name = "TimeoutError";
|
|
14258
|
+
timeoutAbort.abort(error);
|
|
14259
|
+
};
|
|
14260
|
+
const resetIdleTimer = () => {
|
|
14261
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
14262
|
+
idleTimer = setTimeout(
|
|
14263
|
+
() => timeout(`/btw provider was idle for ${BTW_STREAM_IDLE_TIMEOUT_MS / 1000} seconds`),
|
|
14264
|
+
BTW_STREAM_IDLE_TIMEOUT_MS,
|
|
14265
|
+
);
|
|
14266
|
+
idleTimer.unref?.();
|
|
14267
|
+
};
|
|
14268
|
+
const totalTimer = setTimeout(
|
|
14269
|
+
() => timeout(`/btw provider exceeded ${BTW_STREAM_TOTAL_TIMEOUT_MS / 1000} seconds`),
|
|
14270
|
+
BTW_STREAM_TOTAL_TIMEOUT_MS,
|
|
14271
|
+
);
|
|
14272
|
+
totalTimer.unref?.();
|
|
14273
|
+
resetIdleTimer();
|
|
14274
|
+
const consume = async () => {
|
|
14275
|
+
while (active) {
|
|
14276
|
+
const result = await iterator.next();
|
|
14277
|
+
if (result.done || !active) break;
|
|
14278
|
+
resetIdleTimer();
|
|
14279
|
+
const event = result.value;
|
|
14280
|
+
if (event.type === "text_delta") {
|
|
14281
|
+
const bounded = truncateUtf8(replyText + event.delta, BTW_MAX_ANSWER_UTF8_BYTES);
|
|
14282
|
+
const delta = bounded.slice(replyText.length);
|
|
14283
|
+
replyText = bounded;
|
|
14284
|
+
if (delta) onTextDelta?.(delta);
|
|
14285
|
+
} else if (event.type === "done") {
|
|
14286
|
+
completed = true;
|
|
14287
|
+
break;
|
|
14288
|
+
} else if (event.type === "error") {
|
|
14289
|
+
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14290
|
+
}
|
|
14169
14291
|
}
|
|
14292
|
+
};
|
|
14293
|
+
try {
|
|
14294
|
+
await awaitEphemeralAbort(consume(), requestSignal);
|
|
14295
|
+
requestSignal.throwIfAborted();
|
|
14296
|
+
if (!completed) throw new Error("Ephemeral turn ended without a final message");
|
|
14297
|
+
const finalText = replyText.trim();
|
|
14298
|
+
return { replyText: finalText, assistantMessage: this.#buildBtwAssistantMessage(finalText) };
|
|
14299
|
+
} finally {
|
|
14300
|
+
active = false;
|
|
14301
|
+
onTextDelta = undefined;
|
|
14302
|
+
replyText = "";
|
|
14303
|
+
context.messages = [];
|
|
14304
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
14305
|
+
clearTimeout(totalTimer);
|
|
14306
|
+
void iterator.return?.().catch(() => undefined);
|
|
14170
14307
|
}
|
|
14171
14308
|
}
|
|
14172
14309
|
|
|
14173
|
-
/** Build a `/btw` snapshot from an already-cloned committed context. */
|
|
14174
|
-
#buildBtwEphemeralSnapshot(messages: AgentMessage[], promptText: string): AgentMessage[] {
|
|
14175
|
-
messages.push(this.#buildEphemeralPromptMessage(promptText));
|
|
14176
|
-
return messages;
|
|
14177
|
-
}
|
|
14178
|
-
|
|
14179
14310
|
/** Build a background snapshot with in-flight assistant and optional context. */
|
|
14180
14311
|
#buildEphemeralSnapshot(promptText: string, prependMessages?: AgentMessage[]): AgentMessage[] {
|
|
14181
14312
|
const messages = [...this.messages];
|
|
@@ -14416,6 +14547,13 @@ export class AgentSession {
|
|
|
14416
14547
|
await this.sessionManager.ensureOnDisk();
|
|
14417
14548
|
|
|
14418
14549
|
if (switchingToDifferentSession) {
|
|
14550
|
+
// Interactive /resume (and any other switchSession identity change) must
|
|
14551
|
+
// await verified legacy local:// migration for the *new* session before
|
|
14552
|
+
// post-commit session_switch / local path resolution. createAgentSession
|
|
14553
|
+
// already does this at cold start (#2797); switchSession previously omitted
|
|
14554
|
+
// it, so resolving local:// after /resume could fail-closed with
|
|
14555
|
+
// "legacy migration must complete before path resolution" (#2925).
|
|
14556
|
+
await initializeLocalRoot(this.#localProtocolOptions());
|
|
14419
14557
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
14420
14558
|
this.#resetIrcRosterDeliveryState();
|
|
14421
14559
|
}
|
|
@@ -5,6 +5,18 @@ import { isEnoent, logger } from "@gajae-code/utils";
|
|
|
5
5
|
|
|
6
6
|
const BLOB_PREFIX = "blob:sha256:";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Owner-only permissions for on-disk blob directories and files.
|
|
10
|
+
*
|
|
11
|
+
* Blob stores can live inside a managed session scope (e.g. the resident-cache
|
|
12
|
+
* `EphemeralBlobStore` created on the explicit session path). The managed-tree
|
|
13
|
+
* snapshot fails closed unless every descendant is owner-only, so blobs and
|
|
14
|
+
* their directories MUST be created 0700/0600 rather than inheriting a
|
|
15
|
+
* group/other-readable mode from the process umask.
|
|
16
|
+
*/
|
|
17
|
+
const BLOB_DIR_MODE = 0o700;
|
|
18
|
+
const BLOB_FILE_MODE = 0o600;
|
|
19
|
+
|
|
8
20
|
export interface BlobPutResult {
|
|
9
21
|
hash: string;
|
|
10
22
|
path: string;
|
|
@@ -112,7 +124,13 @@ export class BlobStore {
|
|
|
112
124
|
},
|
|
113
125
|
};
|
|
114
126
|
|
|
115
|
-
|
|
127
|
+
// Create the blob directory 0700 and the file 0600 at creation, mirroring
|
|
128
|
+
// putSync/putImmutableSync. The previous Bun.write let the parent dir be
|
|
129
|
+
// created with the process umask (e.g. 0755) and the file at 0644 before a
|
|
130
|
+
// follow-up chmod, violating the owner-only contract above and leaving a
|
|
131
|
+
// group/other-readable window a managed-tree snapshot fails closed on.
|
|
132
|
+
await fsp.mkdir(this.dir, { recursive: true, mode: BLOB_DIR_MODE });
|
|
133
|
+
await fsp.writeFile(blobPath, data, { mode: BLOB_FILE_MODE });
|
|
116
134
|
return result;
|
|
117
135
|
}
|
|
118
136
|
|
|
@@ -131,8 +149,8 @@ export class BlobStore {
|
|
|
131
149
|
return `${BLOB_PREFIX}${hash}`;
|
|
132
150
|
},
|
|
133
151
|
};
|
|
134
|
-
fs.mkdirSync(this.dir, { recursive: true });
|
|
135
|
-
fs.writeFileSync(blobPath, data);
|
|
152
|
+
fs.mkdirSync(this.dir, { recursive: true, mode: BLOB_DIR_MODE });
|
|
153
|
+
fs.writeFileSync(blobPath, data, { mode: BLOB_FILE_MODE });
|
|
136
154
|
return result;
|
|
137
155
|
}
|
|
138
156
|
|
|
@@ -147,7 +165,7 @@ export class BlobStore {
|
|
|
147
165
|
const hash = sha256Hex(data);
|
|
148
166
|
const blobPath = path.join(this.dir, hash);
|
|
149
167
|
const result = makeBlobPutResult(hash, blobPath, data.byteLength);
|
|
150
|
-
fs.mkdirSync(this.dir, { recursive: true });
|
|
168
|
+
fs.mkdirSync(this.dir, { recursive: true, mode: BLOB_DIR_MODE });
|
|
151
169
|
|
|
152
170
|
if (fs.existsSync(blobPath)) {
|
|
153
171
|
verifyBlobFileSync(hash, blobPath);
|
|
@@ -156,7 +174,7 @@ export class BlobStore {
|
|
|
156
174
|
|
|
157
175
|
const tempPath = uniqueTempBlobPath(this.dir, hash);
|
|
158
176
|
try {
|
|
159
|
-
const fd = fs.openSync(tempPath, "wx");
|
|
177
|
+
const fd = fs.openSync(tempPath, "wx", BLOB_FILE_MODE);
|
|
160
178
|
try {
|
|
161
179
|
fs.writeFileSync(fd, data);
|
|
162
180
|
fs.fsyncSync(fd);
|
|
@@ -274,7 +292,7 @@ export class EphemeralBlobStore extends BlobStore {
|
|
|
274
292
|
constructor(dir: string) {
|
|
275
293
|
super(dir);
|
|
276
294
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
277
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
295
|
+
fs.mkdirSync(dir, { recursive: true, mode: BLOB_DIR_MODE });
|
|
278
296
|
}
|
|
279
297
|
|
|
280
298
|
#cachePut(hash: string, data: Buffer): void {
|
|
@@ -333,7 +351,7 @@ export class EphemeralBlobStore extends BlobStore {
|
|
|
333
351
|
this.#bufferCache.clear();
|
|
334
352
|
this.#bufferCacheBytes = 0;
|
|
335
353
|
fs.rmSync(this.dir, { recursive: true, force: true });
|
|
336
|
-
fs.mkdirSync(this.dir, { recursive: true });
|
|
354
|
+
fs.mkdirSync(this.dir, { recursive: true, mode: BLOB_DIR_MODE });
|
|
337
355
|
}
|
|
338
356
|
|
|
339
357
|
dispose(): void {
|