@cjhyy/code-shell-core 0.9.2 → 0.9.4
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/dist/cli/agent-server-stdio.js +26 -1
- package/dist/cli/agent-server-tcp.js +58 -7
- package/dist/engine/engine.d.ts +8 -0
- package/dist/engine/engine.js +16 -12
- package/dist/engine/model-facade.d.ts +3 -0
- package/dist/engine/model-facade.js +2 -0
- package/dist/engine/run-tooling.js +8 -8
- package/dist/engine/streaming-tool-queue.d.ts +4 -1
- package/dist/engine/streaming-tool-queue.js +17 -2
- package/dist/engine/turn-loop.d.ts +14 -5
- package/dist/engine/turn-loop.js +92 -37
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/llm/prompt-cache.d.ts +48 -0
- package/dist/llm/prompt-cache.js +100 -0
- package/dist/llm/providers/anthropic.d.ts +3 -0
- package/dist/llm/providers/anthropic.js +77 -53
- package/dist/llm/providers/openai.d.ts +7 -27
- package/dist/llm/providers/openai.js +120 -68
- package/dist/llm/types.d.ts +3 -0
- package/dist/onboarding.js +72 -49
- package/dist/panel-apps/manifest.d.ts +12 -12
- package/dist/profile/types.d.ts +26 -26
- package/dist/protocol/background-result-wakeup.d.ts +3 -1
- package/dist/protocol/background-result-wakeup.js +5 -5
- package/dist/protocol/server.d.ts +13 -0
- package/dist/protocol/server.js +22 -3
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/session/memory.js +2 -2
- package/dist/session/session-manager.js +30 -1
- package/dist/tool-system/builtin/agent-notifications.d.ts +25 -1
- package/dist/tool-system/builtin/agent-notifications.js +334 -2
- package/dist/tool-system/context.d.ts +9 -4
- package/dist/tool-system/external-tool-exposure.js +11 -10
- package/package.json +2 -1
- package/dist/services/notifier.d.ts +0 -33
- package/dist/services/notifier.js +0 -83
package/dist/profile/types.d.ts
CHANGED
|
@@ -125,6 +125,11 @@ export declare const WorkspaceProfileRequirementsSchema: z.ZodObject<{
|
|
|
125
125
|
hint?: string | undefined;
|
|
126
126
|
}>, "many">>;
|
|
127
127
|
}, "strip", z.ZodTypeAny, {
|
|
128
|
+
tools: {
|
|
129
|
+
bin: string;
|
|
130
|
+
minVersion?: string | undefined;
|
|
131
|
+
hint?: string | undefined;
|
|
132
|
+
}[];
|
|
128
133
|
skills: {
|
|
129
134
|
source: "github";
|
|
130
135
|
scope: "project";
|
|
@@ -132,12 +137,12 @@ export declare const WorkspaceProfileRequirementsSchema: z.ZodObject<{
|
|
|
132
137
|
fullDepth: boolean;
|
|
133
138
|
skills?: string[] | undefined;
|
|
134
139
|
}[];
|
|
135
|
-
|
|
140
|
+
}, {
|
|
141
|
+
tools?: {
|
|
136
142
|
bin: string;
|
|
137
143
|
minVersion?: string | undefined;
|
|
138
144
|
hint?: string | undefined;
|
|
139
|
-
}[];
|
|
140
|
-
}, {
|
|
145
|
+
}[] | undefined;
|
|
141
146
|
skills?: {
|
|
142
147
|
source: "github";
|
|
143
148
|
repo: string;
|
|
@@ -145,11 +150,6 @@ export declare const WorkspaceProfileRequirementsSchema: z.ZodObject<{
|
|
|
145
150
|
skills?: string[] | undefined;
|
|
146
151
|
fullDepth?: boolean | undefined;
|
|
147
152
|
}[] | undefined;
|
|
148
|
-
tools?: {
|
|
149
|
-
bin: string;
|
|
150
|
-
minVersion?: string | undefined;
|
|
151
|
-
hint?: string | undefined;
|
|
152
|
-
}[] | undefined;
|
|
153
153
|
}>;
|
|
154
154
|
export type SkillRequirement = z.infer<typeof skillRequirementSchema>;
|
|
155
155
|
export type ToolRequirement = z.infer<typeof toolRequirementSchema>;
|
|
@@ -206,6 +206,11 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
206
206
|
hint?: string | undefined;
|
|
207
207
|
}>, "many">>;
|
|
208
208
|
}, "strip", z.ZodTypeAny, {
|
|
209
|
+
tools: {
|
|
210
|
+
bin: string;
|
|
211
|
+
minVersion?: string | undefined;
|
|
212
|
+
hint?: string | undefined;
|
|
213
|
+
}[];
|
|
209
214
|
skills: {
|
|
210
215
|
source: "github";
|
|
211
216
|
scope: "project";
|
|
@@ -213,12 +218,12 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
213
218
|
fullDepth: boolean;
|
|
214
219
|
skills?: string[] | undefined;
|
|
215
220
|
}[];
|
|
216
|
-
|
|
221
|
+
}, {
|
|
222
|
+
tools?: {
|
|
217
223
|
bin: string;
|
|
218
224
|
minVersion?: string | undefined;
|
|
219
225
|
hint?: string | undefined;
|
|
220
|
-
}[];
|
|
221
|
-
}, {
|
|
226
|
+
}[] | undefined;
|
|
222
227
|
skills?: {
|
|
223
228
|
source: "github";
|
|
224
229
|
repo: string;
|
|
@@ -226,11 +231,6 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
226
231
|
skills?: string[] | undefined;
|
|
227
232
|
fullDepth?: boolean | undefined;
|
|
228
233
|
}[] | undefined;
|
|
229
|
-
tools?: {
|
|
230
|
-
bin: string;
|
|
231
|
-
minVersion?: string | undefined;
|
|
232
|
-
hint?: string | undefined;
|
|
233
|
-
}[] | undefined;
|
|
234
234
|
}>>;
|
|
235
235
|
/**
|
|
236
236
|
* true → 独占工作面:激活时把**未声明**的 skill/plugin/mcp/agent 显式关掉,
|
|
@@ -264,6 +264,11 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
264
264
|
version?: string | undefined;
|
|
265
265
|
description?: string | undefined;
|
|
266
266
|
requires?: {
|
|
267
|
+
tools: {
|
|
268
|
+
bin: string;
|
|
269
|
+
minVersion?: string | undefined;
|
|
270
|
+
hint?: string | undefined;
|
|
271
|
+
}[];
|
|
267
272
|
skills: {
|
|
268
273
|
source: "github";
|
|
269
274
|
scope: "project";
|
|
@@ -271,11 +276,6 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
271
276
|
fullDepth: boolean;
|
|
272
277
|
skills?: string[] | undefined;
|
|
273
278
|
}[];
|
|
274
|
-
tools: {
|
|
275
|
-
bin: string;
|
|
276
|
-
minVersion?: string | undefined;
|
|
277
|
-
hint?: string | undefined;
|
|
278
|
-
}[];
|
|
279
279
|
} | undefined;
|
|
280
280
|
mainInstruction?: string | undefined;
|
|
281
281
|
}, {
|
|
@@ -289,6 +289,11 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
289
289
|
plugins?: string[] | undefined;
|
|
290
290
|
agents?: string[] | undefined;
|
|
291
291
|
requires?: {
|
|
292
|
+
tools?: {
|
|
293
|
+
bin: string;
|
|
294
|
+
minVersion?: string | undefined;
|
|
295
|
+
hint?: string | undefined;
|
|
296
|
+
}[] | undefined;
|
|
292
297
|
skills?: {
|
|
293
298
|
source: "github";
|
|
294
299
|
repo: string;
|
|
@@ -296,11 +301,6 @@ export declare const WorkspaceProfileSchema: z.ZodObject<{
|
|
|
296
301
|
skills?: string[] | undefined;
|
|
297
302
|
fullDepth?: boolean | undefined;
|
|
298
303
|
}[] | undefined;
|
|
299
|
-
tools?: {
|
|
300
|
-
bin: string;
|
|
301
|
-
minVersion?: string | undefined;
|
|
302
|
-
hint?: string | undefined;
|
|
303
|
-
}[] | undefined;
|
|
304
304
|
} | undefined;
|
|
305
305
|
exclusiveCapabilities?: boolean | undefined;
|
|
306
306
|
mainInstruction?: string | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StreamEvent } from "../types.js";
|
|
2
|
+
import { type NotificationQueue } from "../tool-system/builtin/agent-notifications.js";
|
|
2
3
|
import type { ApprovalRouter } from "../tool-system/permission.js";
|
|
3
4
|
import type { ChatSession } from "./chat-session.js";
|
|
4
5
|
import type { ChatSessionManager } from "./chat-session-manager.js";
|
|
@@ -8,11 +9,12 @@ interface BackgroundResultWakeOptions {
|
|
|
8
9
|
rehydrate(sessionId: string): Promise<ChatSession | null>;
|
|
9
10
|
approvalRouter: ApprovalRouter;
|
|
10
11
|
onStream(event: StreamEvent): void;
|
|
12
|
+
notificationMailbox?: NotificationQueue;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Drain pending background results into exactly one synthetic continuation.
|
|
14
16
|
* Busy sessions are awaited so a completion cannot fall into the gap between
|
|
15
17
|
* the notification bus callback and the interactive run-boundary re-check.
|
|
16
18
|
*/
|
|
17
|
-
export declare function wakeSessionForBackgroundResults({ sessionId, manager, rehydrate, approvalRouter, onStream, }: BackgroundResultWakeOptions): Promise<boolean>;
|
|
19
|
+
export declare function wakeSessionForBackgroundResults({ sessionId, manager, rehydrate, approvalRouter, onStream, notificationMailbox, }: BackgroundResultWakeOptions): Promise<boolean>;
|
|
18
20
|
export {};
|
|
@@ -5,7 +5,7 @@ import { buildNotificationMessage, notificationQueue, } from "../tool-system/bui
|
|
|
5
5
|
* Busy sessions are awaited so a completion cannot fall into the gap between
|
|
6
6
|
* the notification bus callback and the interactive run-boundary re-check.
|
|
7
7
|
*/
|
|
8
|
-
export async function wakeSessionForBackgroundResults({ sessionId, manager, rehydrate, approvalRouter, onStream, }) {
|
|
8
|
+
export async function wakeSessionForBackgroundResults({ sessionId, manager, rehydrate, approvalRouter, onStream, notificationMailbox = notificationQueue, }) {
|
|
9
9
|
if (!manager) {
|
|
10
10
|
logger.debug("bg_wakeup.skipped", { sessionId, reason: "no_chat_manager" });
|
|
11
11
|
return false;
|
|
@@ -22,7 +22,7 @@ export async function wakeSessionForBackgroundResults({ sessionId, manager, rehy
|
|
|
22
22
|
while (session.isBusy()) {
|
|
23
23
|
logger.debug("bg_wakeup.waiting_for_idle", {
|
|
24
24
|
sessionId,
|
|
25
|
-
pendingCount:
|
|
25
|
+
pendingCount: notificationMailbox.getSnapshot(sessionId).length,
|
|
26
26
|
});
|
|
27
27
|
await session.settled;
|
|
28
28
|
if (manager.isUnavailable(sessionId)) {
|
|
@@ -63,7 +63,7 @@ export async function wakeSessionForBackgroundResults({ sessionId, manager, rehy
|
|
|
63
63
|
});
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
|
-
const pending =
|
|
66
|
+
const pending = notificationMailbox.drainAll(sessionId);
|
|
67
67
|
if (pending.length === 0) {
|
|
68
68
|
logger.debug("bg_wakeup.skipped", { sessionId, reason: "no_pending_results" });
|
|
69
69
|
return false;
|
|
@@ -77,7 +77,7 @@ export async function wakeSessionForBackgroundResults({ sessionId, manager, rehy
|
|
|
77
77
|
approvalRouter,
|
|
78
78
|
});
|
|
79
79
|
if (result.turnCount === 0) {
|
|
80
|
-
const restored =
|
|
80
|
+
const restored = notificationMailbox.restoreResults(sessionId, pending);
|
|
81
81
|
logger.warn("bg_wakeup.turn_not_started", {
|
|
82
82
|
sessionId,
|
|
83
83
|
restored,
|
|
@@ -89,7 +89,7 @@ export async function wakeSessionForBackgroundResults({ sessionId, manager, rehy
|
|
|
89
89
|
}
|
|
90
90
|
catch (error) {
|
|
91
91
|
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
-
const restored =
|
|
92
|
+
const restored = notificationMailbox.restoreResults(sessionId, pending);
|
|
93
93
|
logger.warn("bg_wakeup.turn_failed", { sessionId, error: message, restored });
|
|
94
94
|
// A setup failure can occur before the turn loop emits its own terminal
|
|
95
95
|
// event. Emit an error so every renderer clears its busy state, but keep
|
|
@@ -17,6 +17,7 @@ import type { Transport } from "./transport.js";
|
|
|
17
17
|
import type { Engine } from "../engine/engine.js";
|
|
18
18
|
import type { ValidatedSettings } from "../settings/schema.js";
|
|
19
19
|
import { type ApprovalRouter } from "../tool-system/permission.js";
|
|
20
|
+
import { type NotificationQueue, type NotificationQueuePersistence } from "../tool-system/builtin/agent-notifications.js";
|
|
20
21
|
import type { ChatSessionManager } from "./chat-session-manager.js";
|
|
21
22
|
import type { ResolvedComposition } from "../composition/types.js";
|
|
22
23
|
/**
|
|
@@ -106,6 +107,16 @@ export interface AgentServerOptions {
|
|
|
106
107
|
* write.
|
|
107
108
|
*/
|
|
108
109
|
sessionDiskRoot?: string;
|
|
110
|
+
/** Durable per-session result mailbox. Omitted keeps the in-memory behavior. */
|
|
111
|
+
notificationPersistence?: NotificationQueuePersistence;
|
|
112
|
+
/** Test/host seam; production uses the process singleton. */
|
|
113
|
+
notificationMailbox?: NotificationQueue;
|
|
114
|
+
/**
|
|
115
|
+
* Whether this server instance owns durable result restoration and idle
|
|
116
|
+
* wakeups. Defaults to true. Multi-connection hosts set one process-lifetime
|
|
117
|
+
* owner and leave connection servers as observation-only forwarders.
|
|
118
|
+
*/
|
|
119
|
+
ownsBackgroundWakeups?: boolean;
|
|
109
120
|
/** Shared owner router; injectable for hosts/tests, process singleton by default. */
|
|
110
121
|
approvalRouter?: ApprovalRouter;
|
|
111
122
|
/**
|
|
@@ -197,8 +208,10 @@ export declare class AgentServer {
|
|
|
197
208
|
* itself outlives the server (it's a process-local singleton).
|
|
198
209
|
*/
|
|
199
210
|
private bgAgentBusUnsubscribe;
|
|
211
|
+
private readonly ownsBackgroundWakeups;
|
|
200
212
|
private readonly wakeupsInFlight;
|
|
201
213
|
private readonly sessionWorkspaceRpc;
|
|
214
|
+
private readonly notificationMailbox;
|
|
202
215
|
/**
|
|
203
216
|
* Effective session manager for the connection this server serves. Without
|
|
204
217
|
* a resolveIdentity hook this is exactly the host-supplied manager —
|
package/dist/protocol/server.js
CHANGED
|
@@ -381,8 +381,10 @@ export class AgentServer {
|
|
|
381
381
|
* itself outlives the server (it's a process-local singleton).
|
|
382
382
|
*/
|
|
383
383
|
bgAgentBusUnsubscribe = null;
|
|
384
|
+
ownsBackgroundWakeups;
|
|
384
385
|
wakeupsInFlight = new Set();
|
|
385
386
|
sessionWorkspaceRpc;
|
|
387
|
+
notificationMailbox;
|
|
386
388
|
/**
|
|
387
389
|
* Effective session manager for the connection this server serves. Without
|
|
388
390
|
* a resolveIdentity hook this is exactly the host-supplied manager —
|
|
@@ -407,6 +409,11 @@ export class AgentServer {
|
|
|
407
409
|
return manager;
|
|
408
410
|
}
|
|
409
411
|
constructor(options) {
|
|
412
|
+
this.notificationMailbox = options.notificationMailbox ?? notificationQueue;
|
|
413
|
+
this.ownsBackgroundWakeups = options.ownsBackgroundWakeups !== false;
|
|
414
|
+
if (options.notificationPersistence) {
|
|
415
|
+
this.notificationMailbox.attachPersistence(options.notificationPersistence);
|
|
416
|
+
}
|
|
410
417
|
this.baseChatManager = options.chatManager ?? null;
|
|
411
418
|
this.resolveIdentity = options.resolveIdentity ?? null;
|
|
412
419
|
this.sessionDiskRoot = options.sessionDiskRoot;
|
|
@@ -509,8 +516,9 @@ export class AgentServer {
|
|
|
509
516
|
// legacy UI event is only an observation path. A renderer transport
|
|
510
517
|
// failure after accepting the event must not strand the result in the
|
|
511
518
|
// queue, so schedule the wake from finally.
|
|
512
|
-
if (envelope.kind === "result")
|
|
519
|
+
if (this.ownsBackgroundWakeups && envelope.kind === "result") {
|
|
513
520
|
this.maybeWakeIdleSession(sessionId);
|
|
521
|
+
}
|
|
514
522
|
}
|
|
515
523
|
// Background work that finishes while the session is idle (a
|
|
516
524
|
// run_in_background Bash like a download, a background sub-agent, or a
|
|
@@ -523,6 +531,14 @@ export class AgentServer {
|
|
|
523
531
|
// continuation. A never-exiting dev server emits no completion, so it
|
|
524
532
|
// never wakes anything (no task/service classification needed).
|
|
525
533
|
});
|
|
534
|
+
// Results restored from disk are deliberately not republished on the
|
|
535
|
+
// observation bus: their required destination is the original chat. Feed
|
|
536
|
+
// them into the same guarded wake path as live completions instead.
|
|
537
|
+
if (this.ownsBackgroundWakeups) {
|
|
538
|
+
for (const sessionId of this.notificationMailbox.restorePersistedSessions()) {
|
|
539
|
+
this.maybeWakeIdleSession(sessionId);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
526
542
|
// Notify client we're ready
|
|
527
543
|
this.notify(Methods.Status, { status: "ready" });
|
|
528
544
|
}
|
|
@@ -549,13 +565,15 @@ export class AgentServer {
|
|
|
549
565
|
* wakeup turns while getOrCreate waits for a closing generation to settle.
|
|
550
566
|
*/
|
|
551
567
|
maybeWakeIdleSession(sessionId) {
|
|
568
|
+
if (!this.ownsBackgroundWakeups)
|
|
569
|
+
return;
|
|
552
570
|
if (this.wakeupsInFlight.has(sessionId))
|
|
553
571
|
return;
|
|
554
572
|
this.wakeupsInFlight.add(sessionId);
|
|
555
573
|
void this.wakeIdleSession(sessionId)
|
|
556
574
|
.then((ranTurn) => {
|
|
557
575
|
this.wakeupsInFlight.delete(sessionId);
|
|
558
|
-
if (ranTurn &&
|
|
576
|
+
if (ranTurn && this.notificationMailbox.getSnapshot(sessionId).length > 0) {
|
|
559
577
|
this.maybeWakeIdleSession(sessionId);
|
|
560
578
|
}
|
|
561
579
|
})
|
|
@@ -574,6 +592,7 @@ export class AgentServer {
|
|
|
574
592
|
rehydrate: (id) => this.rehydrateSessionForWake(id),
|
|
575
593
|
approvalRouter: this.approvalRouter,
|
|
576
594
|
onStream: (event) => this.notify(Methods.StreamEvent, { sessionId, event }),
|
|
595
|
+
notificationMailbox: this.notificationMailbox,
|
|
577
596
|
});
|
|
578
597
|
}
|
|
579
598
|
async rehydrateSessionForWake(sessionId) {
|
|
@@ -582,7 +601,7 @@ export class AgentServer {
|
|
|
582
601
|
if (this.chatManager.isUnavailable(sessionId))
|
|
583
602
|
return null;
|
|
584
603
|
try {
|
|
585
|
-
const pending =
|
|
604
|
+
const pending = this.notificationMailbox.getSnapshot(sessionId);
|
|
586
605
|
if (pending.length === 0) {
|
|
587
606
|
logger.debug("bg_wakeup.rehydrate_skipped_no_pending", { sessionId });
|
|
588
607
|
return null;
|
package/dist/services/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { analytics, trackEvent } from "./analytics.js";
|
|
5
5
|
export { authorize, refreshToken, generatePKCE, createHardenedOAuthFetch, type OAuthConfig, type OAuthTokens, type OAuthAuthorizeOptions, type OAuthRefreshOptions, type HardenedOAuthFetchOptions, } from "./oauth.js";
|
|
6
|
-
export { notify, notifyComplete, notifyError } from "./notifier.js";
|
|
7
6
|
export { diagnostics } from "./diagnostics.js";
|
|
8
7
|
export { buildExtractionPrompt, parseExtractionResponse, type ExtractedMemory, } from "./extract-memories.js";
|
|
9
8
|
export { shouldAutoDream, recordSession, recordDreamComplete, buildDreamSystemPrompt, buildDreamUserPrompt, } from "./auto-dream.js";
|
package/dist/services/index.js
CHANGED
|
@@ -9,7 +9,6 @@ export { analytics, trackEvent } from "./analytics.js";
|
|
|
9
9
|
// to the tool_result-based microcompact in ContextManager). Removed to end
|
|
10
10
|
// the "which microcompact?" ambiguity — the source of truth is ContextManager.
|
|
11
11
|
export { authorize, refreshToken, generatePKCE, createHardenedOAuthFetch, } from "./oauth.js";
|
|
12
|
-
export { notify, notifyComplete, notifyError } from "./notifier.js";
|
|
13
12
|
export { diagnostics } from "./diagnostics.js";
|
|
14
13
|
export { buildExtractionPrompt, parseExtractionResponse, } from "./extract-memories.js";
|
|
15
14
|
export { shouldAutoDream, recordSession, recordDreamComplete, buildDreamSystemPrompt, buildDreamUserPrompt, } from "./auto-dream.js";
|
package/dist/session/memory.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* Deletes are SOFT — files are moved to <baseDir>/memory-trash/<ISO>/<scope>/
|
|
24
24
|
* rather than removed, so accidental deletions are recoverable.
|
|
25
25
|
*/
|
|
26
|
-
import { mkdirSync, existsSync, readFileSync,
|
|
26
|
+
import { mkdirSync, existsSync, readFileSync, readdirSync, renameSync, statSync } from "node:fs";
|
|
27
27
|
import { join } from "node:path";
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -174,7 +174,7 @@ export class MemoryManager {
|
|
|
174
174
|
frontmatterLine("updateCount", updateCount) +
|
|
175
175
|
`---\n\n` +
|
|
176
176
|
`${entry.content}\n`;
|
|
177
|
-
|
|
177
|
+
writeFileAtomic(filePath, content, 0o600);
|
|
178
178
|
// Incremental index update. Initialize the cache on first
|
|
179
179
|
// save by paying one loadAll() — every save after that just
|
|
180
180
|
// mutates the cache and rewrites MEMORY.md.
|
|
@@ -237,13 +237,42 @@ function normalizedSessionKind(kind) {
|
|
|
237
237
|
export function codeShellHome() {
|
|
238
238
|
return process.env.CODE_SHELL_HOME || join(homedir(), ".code-shell");
|
|
239
239
|
}
|
|
240
|
+
/** True when this process is a test runner. `bun test` sets NODE_ENV itself. */
|
|
241
|
+
function isTestRunner() {
|
|
242
|
+
return process.env.NODE_ENV === "test" || process.env.BUN_TEST === "1";
|
|
243
|
+
}
|
|
244
|
+
/** The real per-user home, ignoring any CODE_SHELL_HOME override. */
|
|
245
|
+
function defaultHome() {
|
|
246
|
+
return join(homedir(), ".code-shell");
|
|
247
|
+
}
|
|
240
248
|
/**
|
|
241
249
|
* Canonical root for every persisted CodeShell session. An explicit `home`
|
|
242
250
|
* (a `~/.code-shell`-equivalent data root) overrides the default resolution;
|
|
243
251
|
* absent → `codeShellHome()` exactly as before.
|
|
244
252
|
*/
|
|
245
253
|
export function sessionsRoot(home) {
|
|
246
|
-
|
|
254
|
+
const resolvedHome = home ?? codeShellHome();
|
|
255
|
+
// Fail closed rather than write fixture sessions into the developer's real
|
|
256
|
+
// store. The bunfig preload only applies when Bun resolves a bunfig.toml:
|
|
257
|
+
// `bun test <file>` from a subdirectory, or an IDE/CI runner with its own
|
|
258
|
+
// cwd, finds none and silently fell through to ~/.code-shell — which is how
|
|
259
|
+
// ~700 `test-model` sessions ended up in the real store and the sidebar.
|
|
260
|
+
//
|
|
261
|
+
// Guarded here rather than in codeShellHome() because the home is also the
|
|
262
|
+
// legitimate root for settings/projects/trust that many tests read by
|
|
263
|
+
// design; only the SESSIONS root is the one tests corrupt.
|
|
264
|
+
if (isTestRunner() && resolvedHome === defaultHome()) {
|
|
265
|
+
// Prefer the preload's sandbox. Many test files delete CODE_SHELL_HOME in
|
|
266
|
+
// cleanup, and bun shares one process across files, so isolation would
|
|
267
|
+
// otherwise vanish for every later suite.
|
|
268
|
+
const sandbox = process.env.CODE_SHELL_TEST_HOME;
|
|
269
|
+
if (sandbox)
|
|
270
|
+
return join(sandbox, "sessions");
|
|
271
|
+
throw new SessionError("Refusing to use the real ~/.code-shell/sessions from a test. Set CODE_SHELL_HOME " +
|
|
272
|
+
"to a temp dir (packages/core/test-setup.ts does this via the bunfig preload) " +
|
|
273
|
+
"or pass an explicit storageDir.");
|
|
274
|
+
}
|
|
275
|
+
return join(resolvedHome, "sessions");
|
|
247
276
|
}
|
|
248
277
|
function isSessionWorkspace(value) {
|
|
249
278
|
if (!value || typeof value !== "object")
|
|
@@ -119,12 +119,23 @@ export type NotificationItem = {
|
|
|
119
119
|
enqueuedAt: number;
|
|
120
120
|
};
|
|
121
121
|
type Listener = () => void;
|
|
122
|
-
|
|
122
|
+
export interface NotificationQueuePersistence {
|
|
123
|
+
fileForSession(sessionId: string): string | null;
|
|
124
|
+
/** Optional startup inventory. Lazy per-session restore works without it. */
|
|
125
|
+
listSessionIds?(): readonly string[];
|
|
126
|
+
}
|
|
127
|
+
export declare class NotificationQueue {
|
|
123
128
|
private buckets;
|
|
124
129
|
private listeners;
|
|
125
130
|
private sequences;
|
|
126
131
|
private sequenceRoutes;
|
|
132
|
+
private persistence;
|
|
133
|
+
private restoredSessions;
|
|
127
134
|
private readonly maxSequenceRoutes;
|
|
135
|
+
attachPersistence(persistence: NotificationQueuePersistence | null): void;
|
|
136
|
+
/** Restore every persisted mailbox discovered by the host during startup. */
|
|
137
|
+
restorePersistedSessions(): string[];
|
|
138
|
+
restorePersistedSession(sessionId: string): number;
|
|
128
139
|
enqueue(draft: NotificationEnvelopeDraft): NotificationEnvelope | undefined;
|
|
129
140
|
enqueue(item: NotificationItem, sessionId: string): ResultEnvelope | undefined;
|
|
130
141
|
subscribe: (listener: Listener) => (() => void);
|
|
@@ -143,6 +154,19 @@ declare class NotificationQueue {
|
|
|
143
154
|
clearProgress(sessionId: string, agentId: string, runtimeGeneration?: number): boolean;
|
|
144
155
|
clearDirections(sessionId: string, runtimeGeneration: number): boolean;
|
|
145
156
|
reset(sessionId?: string): void;
|
|
157
|
+
private resultSnapshot;
|
|
158
|
+
private persistAddedResults;
|
|
159
|
+
private persistRemovedResults;
|
|
160
|
+
private replacePersistedResults;
|
|
161
|
+
/**
|
|
162
|
+
* Cross-process mailbox mutation. The directory lock exists before the JSON
|
|
163
|
+
* file does, and the current contents are re-read inside that lock. This
|
|
164
|
+
* avoids both the old lock-outside seed race and stale-snapshot overwrite.
|
|
165
|
+
*/
|
|
166
|
+
private mutatePersistedResults;
|
|
167
|
+
private parsePersistedResultsForMutation;
|
|
168
|
+
private quarantineCorruptFile;
|
|
169
|
+
private reseedSequence;
|
|
146
170
|
private notify;
|
|
147
171
|
}
|
|
148
172
|
type EnvelopeBusHandler = (envelope: NotificationEnvelope) => void;
|