@clinebot/core 0.0.32 → 0.0.34
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/auth/client.d.ts +19 -0
- package/dist/auth/client.d.ts.map +1 -1
- package/dist/auth/cline.d.ts.map +1 -1
- package/dist/auth/oca.d.ts.map +1 -1
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -1
- package/dist/auth/types.d.ts +29 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/extensions/context/agentic-compaction.d.ts.map +1 -1
- package/dist/extensions/context/basic-compaction.d.ts.map +1 -1
- package/dist/extensions/context/compaction-shared.d.ts +1 -1
- package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
- package/dist/extensions/context/compaction.d.ts.map +1 -1
- package/dist/extensions/index.d.ts +2 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts +2 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-load-report.d.ts +19 -0
- package/dist/extensions/plugin/plugin-load-report.d.ts.map +1 -0
- package/dist/extensions/plugin/plugin-loader.d.ts +6 -0
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +2 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin-sandbox-bootstrap.js +148 -148
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +227 -229
- package/dist/runtime/runtime-builder.d.ts +1 -1
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/subprocess-sandbox.d.ts +2 -0
- package/dist/runtime/subprocess-sandbox.d.ts.map +1 -1
- package/dist/runtime/tool-approval.d.ts.map +1 -1
- package/dist/session/default-session-manager.d.ts.map +1 -1
- package/dist/session/persistence-service.d.ts.map +1 -1
- package/dist/session/session-agent-events.d.ts.map +1 -1
- package/dist/session/session-artifacts.d.ts +2 -0
- package/dist/session/session-artifacts.d.ts.map +1 -1
- package/dist/session/session-config-builder.d.ts.map +1 -1
- package/dist/team/team-tools.d.ts.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/events.d.ts +4 -0
- package/dist/types/events.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/auth/client.test.ts +29 -0
- package/src/auth/client.ts +21 -0
- package/src/auth/cline.ts +2 -0
- package/src/auth/oca.ts +2 -0
- package/src/auth/server.test.ts +287 -0
- package/src/auth/server.ts +50 -1
- package/src/auth/types.ts +29 -0
- package/src/extensions/context/agentic-compaction.ts +22 -10
- package/src/extensions/context/basic-compaction.ts +43 -18
- package/src/extensions/context/compaction-shared.ts +1 -1
- package/src/extensions/context/compaction.test.ts +16 -10
- package/src/extensions/context/compaction.ts +35 -12
- package/src/extensions/index.ts +6 -0
- package/src/extensions/plugin/plugin-config-loader.test.ts +37 -0
- package/src/extensions/plugin/plugin-config-loader.ts +18 -10
- package/src/extensions/plugin/plugin-load-report.ts +20 -0
- package/src/extensions/plugin/plugin-loader.test.ts +45 -0
- package/src/extensions/plugin/plugin-loader.ts +57 -3
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +158 -86
- package/src/extensions/plugin/plugin-sandbox.test.ts +70 -0
- package/src/extensions/plugin/plugin-sandbox.ts +17 -6
- package/src/index.ts +11 -0
- package/src/providers/local-provider-service.test.ts +4 -4
- package/src/runtime/hook-file-hooks.test.ts +42 -7
- package/src/runtime/runtime-builder.test.ts +98 -0
- package/src/runtime/runtime-builder.ts +112 -65
- package/src/runtime/subprocess-sandbox.ts +26 -23
- package/src/runtime/tool-approval.ts +13 -15
- package/src/session/default-session-manager.ts +1 -3
- package/src/session/persistence-service.test.ts +38 -0
- package/src/session/persistence-service.ts +16 -1
- package/src/session/session-agent-events.ts +9 -1
- package/src/session/session-artifacts.ts +16 -0
- package/src/session/session-config-builder.ts +46 -0
- package/src/team/team-tools.test.ts +104 -0
- package/src/team/team-tools.ts +35 -16
- package/src/types/config.ts +1 -0
- package/src/types/events.ts +4 -0
- package/dist/runtime/team-runtime-registry.d.ts +0 -13
- package/dist/runtime/team-runtime-registry.d.ts.map +0 -1
- package/src/runtime/team-runtime-registry.ts +0 -43
|
@@ -39,7 +39,6 @@ import type {
|
|
|
39
39
|
RuntimeBuilderInput,
|
|
40
40
|
BuiltRuntime as RuntimeEnvironment,
|
|
41
41
|
} from "./session-runtime";
|
|
42
|
-
import { TeamRuntimeRegistry } from "./team-runtime-registry";
|
|
43
42
|
|
|
44
43
|
type SkillsExecutorMetadataItem = {
|
|
45
44
|
id: string;
|
|
@@ -52,6 +51,29 @@ type SkillsExecutorWithMetadata = SkillsExecutor & {
|
|
|
52
51
|
configuredSkills?: SkillsExecutorMetadataItem[];
|
|
53
52
|
};
|
|
54
53
|
|
|
54
|
+
function isToolEnabledByPolicies(
|
|
55
|
+
toolName: string,
|
|
56
|
+
toolPolicies: CoreSessionConfig["toolPolicies"],
|
|
57
|
+
): boolean {
|
|
58
|
+
const globalPolicy = toolPolicies?.["*"] ?? {};
|
|
59
|
+
const toolPolicy = toolPolicies?.[toolName] ?? {};
|
|
60
|
+
return (
|
|
61
|
+
{
|
|
62
|
+
...globalPolicy,
|
|
63
|
+
...toolPolicy,
|
|
64
|
+
}.enabled !== false
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function filterToolsByPolicies(
|
|
69
|
+
tools: Tool[],
|
|
70
|
+
toolPolicies: CoreSessionConfig["toolPolicies"],
|
|
71
|
+
): Tool[] {
|
|
72
|
+
return tools.filter((tool) =>
|
|
73
|
+
isToolEnabledByPolicies(tool.name, toolPolicies),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
export function createTeamName(): string {
|
|
56
78
|
return `team-${nanoid(5)}`;
|
|
57
79
|
}
|
|
@@ -63,6 +85,7 @@ function createBuiltinToolsList(
|
|
|
63
85
|
yolo: boolean | undefined,
|
|
64
86
|
modelId: string,
|
|
65
87
|
toolRoutingRules: ToolRoutingRule[] | undefined,
|
|
88
|
+
toolPolicies: CoreSessionConfig["toolPolicies"],
|
|
66
89
|
skillsExecutor?: SkillsExecutorWithMetadata,
|
|
67
90
|
executorOverrides?: Partial<ToolExecutors>,
|
|
68
91
|
): Tool[] {
|
|
@@ -74,20 +97,23 @@ function createBuiltinToolsList(
|
|
|
74
97
|
toolRoutingRules ?? DEFAULT_MODEL_TOOL_ROUTING_RULES,
|
|
75
98
|
);
|
|
76
99
|
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
return filterToolsByPolicies(
|
|
101
|
+
createBuiltinTools({
|
|
102
|
+
cwd,
|
|
103
|
+
...preset,
|
|
104
|
+
enableSkills: !!skillsExecutor,
|
|
105
|
+
...toolRoutingConfig,
|
|
106
|
+
executors: {
|
|
107
|
+
...(skillsExecutor
|
|
108
|
+
? {
|
|
109
|
+
skills: skillsExecutor,
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
...(executorOverrides ?? {}),
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
toolPolicies,
|
|
116
|
+
);
|
|
91
117
|
}
|
|
92
118
|
|
|
93
119
|
const SKILL_FILE_NAME = "SKILL.md";
|
|
@@ -96,7 +122,7 @@ function listAvailableSkillNames(
|
|
|
96
122
|
watcher: UserInstructionConfigWatcher,
|
|
97
123
|
allowedSkillNames?: ReadonlyArray<string>,
|
|
98
124
|
): string[] {
|
|
99
|
-
return
|
|
125
|
+
return getConfiguredSkills(watcher, allowedSkillNames)
|
|
100
126
|
.filter((skill) => !skill.disabled)
|
|
101
127
|
.map((skill) => skill.name.trim())
|
|
102
128
|
.filter((name) => name.length > 0)
|
|
@@ -143,10 +169,14 @@ function isSkillAllowed(
|
|
|
143
169
|
);
|
|
144
170
|
}
|
|
145
171
|
|
|
146
|
-
|
|
172
|
+
type ConfiguredSkill = SkillsExecutorMetadataItem & {
|
|
173
|
+
skill: SkillConfig;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function getConfiguredSkills(
|
|
147
177
|
watcher: UserInstructionConfigWatcher,
|
|
148
178
|
allowedSkillNames?: ReadonlyArray<string>,
|
|
149
|
-
):
|
|
179
|
+
): ConfiguredSkill[] {
|
|
150
180
|
const allowedSkills = toAllowedSkillSet(allowedSkillNames);
|
|
151
181
|
const snapshot = watcher.getSnapshot("skill");
|
|
152
182
|
return [...snapshot.entries()]
|
|
@@ -157,6 +187,7 @@ function listConfiguredSkills(
|
|
|
157
187
|
name: skill.name.trim(),
|
|
158
188
|
description: skill.description?.trim(),
|
|
159
189
|
disabled: skill.disabled === true,
|
|
190
|
+
skill,
|
|
160
191
|
};
|
|
161
192
|
})
|
|
162
193
|
.filter((skill) => isSkillAllowed(skill.id, skill.name, allowedSkills));
|
|
@@ -254,28 +285,22 @@ function resolveSkillRecord(
|
|
|
254
285
|
requestedSkill: string,
|
|
255
286
|
allowedSkillNames?: ReadonlyArray<string>,
|
|
256
287
|
): { id: string; skill: SkillConfig } | { error: string } {
|
|
257
|
-
const allowedSkills = toAllowedSkillSet(allowedSkillNames);
|
|
258
288
|
const normalized = requestedSkill.trim().replace(/^\/+/, "").toLowerCase();
|
|
259
289
|
if (!normalized) {
|
|
260
290
|
return { error: "Missing skill name." };
|
|
261
291
|
}
|
|
262
292
|
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const skill = record.item as SkillConfig;
|
|
266
|
-
return isSkillAllowed(id, skill.name, allowedSkills);
|
|
267
|
-
});
|
|
268
|
-
const scopedSnapshot = new Map(scopedEntries);
|
|
269
|
-
const exact = scopedSnapshot.get(normalized);
|
|
293
|
+
const configuredSkills = getConfiguredSkills(watcher, allowedSkillNames);
|
|
294
|
+
const exact = configuredSkills.find((entry) => entry.id === normalized);
|
|
270
295
|
if (exact) {
|
|
271
|
-
const skill = exact
|
|
296
|
+
const { skill } = exact;
|
|
272
297
|
if (skill.disabled === true) {
|
|
273
298
|
return {
|
|
274
299
|
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
275
300
|
};
|
|
276
301
|
}
|
|
277
302
|
return {
|
|
278
|
-
id:
|
|
303
|
+
id: exact.id,
|
|
279
304
|
skill,
|
|
280
305
|
};
|
|
281
306
|
}
|
|
@@ -284,30 +309,38 @@ function resolveSkillRecord(
|
|
|
284
309
|
? (normalized.split(":").at(-1) ?? normalized)
|
|
285
310
|
: normalized;
|
|
286
311
|
|
|
287
|
-
const suffixMatches =
|
|
312
|
+
const suffixMatches = configuredSkills.filter(({ id }) => {
|
|
288
313
|
if (id === bareName) {
|
|
289
314
|
return true;
|
|
290
315
|
}
|
|
291
316
|
return id.endsWith(`:${bareName}`);
|
|
292
317
|
});
|
|
293
318
|
|
|
319
|
+
const enabledSuffixMatches = suffixMatches.filter(
|
|
320
|
+
({ skill }) => skill.disabled !== true,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
if (enabledSuffixMatches.length === 1) {
|
|
324
|
+
const { id, skill } = enabledSuffixMatches[0];
|
|
325
|
+
return { id, skill };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (enabledSuffixMatches.length > 1) {
|
|
329
|
+
return {
|
|
330
|
+
error: `Skill "${requestedSkill}" is ambiguous. Use one of: ${enabledSuffixMatches.map(({ id }) => id).join(", ")}`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
294
334
|
if (suffixMatches.length === 1) {
|
|
295
|
-
const
|
|
296
|
-
const skill = record.item as SkillConfig;
|
|
297
|
-
if (skill.disabled === true) {
|
|
298
|
-
return {
|
|
299
|
-
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
335
|
+
const { skill } = suffixMatches[0];
|
|
302
336
|
return {
|
|
303
|
-
|
|
304
|
-
skill,
|
|
337
|
+
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
305
338
|
};
|
|
306
339
|
}
|
|
307
340
|
|
|
308
341
|
if (suffixMatches.length > 1) {
|
|
309
342
|
return {
|
|
310
|
-
error: `Skill "${requestedSkill}" is ambiguous
|
|
343
|
+
error: `Skill "${requestedSkill}" is ambiguous, and all matches are disabled: ${suffixMatches.map(({ id }) => id).join(", ")}`,
|
|
311
344
|
};
|
|
312
345
|
}
|
|
313
346
|
|
|
@@ -354,7 +387,10 @@ function createSkillsExecutor(
|
|
|
354
387
|
}
|
|
355
388
|
};
|
|
356
389
|
Object.defineProperty(executor, "configuredSkills", {
|
|
357
|
-
get: () =>
|
|
390
|
+
get: () =>
|
|
391
|
+
getConfiguredSkills(watcher, allowedSkillNames).map(
|
|
392
|
+
({ skill: _skill, ...metadata }) => metadata,
|
|
393
|
+
),
|
|
358
394
|
enumerable: true,
|
|
359
395
|
configurable: false,
|
|
360
396
|
});
|
|
@@ -386,6 +422,7 @@ function normalizeConfig(
|
|
|
386
422
|
| "enableTools"
|
|
387
423
|
| "enableSpawnAgent"
|
|
388
424
|
| "enableAgentTeams"
|
|
425
|
+
| "disableMcpSettingsTools"
|
|
389
426
|
| "yolo"
|
|
390
427
|
| "missionLogIntervalSteps"
|
|
391
428
|
| "missionLogIntervalMs"
|
|
@@ -407,6 +444,7 @@ function normalizeConfig(
|
|
|
407
444
|
config.enableSpawnAgent ?? preset.enableSpawnAgent ?? true,
|
|
408
445
|
enableAgentTeams:
|
|
409
446
|
config.enableAgentTeams ?? preset.enableAgentTeams ?? true,
|
|
447
|
+
disableMcpSettingsTools: config.disableMcpSettingsTools === true,
|
|
410
448
|
yolo: config.yolo === true,
|
|
411
449
|
missionLogIntervalSteps:
|
|
412
450
|
typeof config.missionLogIntervalSteps === "number" &&
|
|
@@ -422,7 +460,15 @@ function normalizeConfig(
|
|
|
422
460
|
}
|
|
423
461
|
|
|
424
462
|
export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
425
|
-
private readonly
|
|
463
|
+
private readonly teamRuntimeEntries = new Map<
|
|
464
|
+
string,
|
|
465
|
+
{
|
|
466
|
+
runtime?: AgentTeamsRuntime;
|
|
467
|
+
delegatedAgentConfigProvider: ReturnType<
|
|
468
|
+
typeof createDelegatedAgentConfigProvider
|
|
469
|
+
>;
|
|
470
|
+
}
|
|
471
|
+
>();
|
|
426
472
|
|
|
427
473
|
async build(input: RuntimeBuilderInput): Promise<RuntimeEnvironment> {
|
|
428
474
|
const {
|
|
@@ -440,6 +486,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
440
486
|
const normalized = normalizeConfig(config);
|
|
441
487
|
const tools: Tool[] = [];
|
|
442
488
|
const effectiveTeamName = config.teamName?.trim() || createTeamName();
|
|
489
|
+
const hasLocalSkills = hasSkillsFiles(config.cwd);
|
|
443
490
|
let teamToolsRegistered = false;
|
|
444
491
|
const watcherProvided = Boolean(sharedUserInstructionWatcher);
|
|
445
492
|
let userInstructionWatcher = sharedUserInstructionWatcher;
|
|
@@ -447,11 +494,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
447
494
|
let skillsExecutor: SkillsExecutorWithMetadata | undefined;
|
|
448
495
|
let mcpShutdown: (() => Promise<void>) | undefined;
|
|
449
496
|
|
|
450
|
-
if (
|
|
451
|
-
!userInstructionWatcher &&
|
|
452
|
-
normalized.enableTools &&
|
|
453
|
-
hasSkillsFiles(config.cwd)
|
|
454
|
-
) {
|
|
497
|
+
if (!userInstructionWatcher && normalized.enableTools && hasLocalSkills) {
|
|
455
498
|
userInstructionWatcher = createUserInstructionConfigWatcher({
|
|
456
499
|
skills: { workspacePath: config.cwd },
|
|
457
500
|
rules: { workspacePath: config.cwd },
|
|
@@ -464,8 +507,8 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
464
507
|
normalized.enableTools &&
|
|
465
508
|
userInstructionWatcher &&
|
|
466
509
|
(watcherProvided ||
|
|
467
|
-
|
|
468
|
-
|
|
510
|
+
hasLocalSkills ||
|
|
511
|
+
getConfiguredSkills(userInstructionWatcher, config.skills).length > 0)
|
|
469
512
|
) {
|
|
470
513
|
skillsExecutor = createSkillsExecutor(
|
|
471
514
|
userInstructionWatcher,
|
|
@@ -483,13 +526,16 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
483
526
|
normalized.yolo,
|
|
484
527
|
config.modelId,
|
|
485
528
|
config.toolRoutingRules,
|
|
529
|
+
config.toolPolicies,
|
|
486
530
|
skillsExecutor,
|
|
487
531
|
defaultToolExecutors,
|
|
488
532
|
),
|
|
489
533
|
);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
534
|
+
if (!normalized.disableMcpSettingsTools) {
|
|
535
|
+
const mcpRuntime = await loadConfiguredMcpTools(config.logger);
|
|
536
|
+
tools.push(...mcpRuntime.tools);
|
|
537
|
+
mcpShutdown = mcpRuntime.shutdown;
|
|
538
|
+
}
|
|
493
539
|
}
|
|
494
540
|
|
|
495
541
|
let teamRuntime: AgentTeamsRuntime | undefined;
|
|
@@ -526,21 +572,21 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
526
572
|
telemetry: input.telemetry ?? config.telemetry,
|
|
527
573
|
workspaceMetadata: config.workspaceMetadata,
|
|
528
574
|
});
|
|
529
|
-
this.
|
|
530
|
-
|
|
531
|
-
|
|
575
|
+
if (!this.teamRuntimeEntries.has(registryKey)) {
|
|
576
|
+
this.teamRuntimeEntries.set(registryKey, {
|
|
577
|
+
delegatedAgentConfigProvider,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
532
580
|
|
|
533
581
|
const ensureTeamRuntime = (): AgentTeamsRuntime | undefined => {
|
|
534
582
|
if (!normalized.enableAgentTeams) {
|
|
535
583
|
return undefined;
|
|
536
584
|
}
|
|
537
585
|
|
|
538
|
-
const registryEntry = this.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}),
|
|
543
|
-
);
|
|
586
|
+
const registryEntry = this.teamRuntimeEntries.get(registryKey) ?? {
|
|
587
|
+
delegatedAgentConfigProvider,
|
|
588
|
+
};
|
|
589
|
+
this.teamRuntimeEntries.set(registryKey, registryEntry);
|
|
544
590
|
teamRuntime = registryEntry.runtime;
|
|
545
591
|
|
|
546
592
|
if (!teamRuntime) {
|
|
@@ -592,7 +638,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
592
638
|
const factory = input.teamToolsFactory ?? bootstrapAgentTeams;
|
|
593
639
|
const teamBootstrap = factory({
|
|
594
640
|
runtime: teamRuntime,
|
|
595
|
-
leadAgentId: "lead",
|
|
641
|
+
leadAgentId: config.sessionId || "lead",
|
|
596
642
|
restoredFromPersistence: Boolean(restoredTeamState),
|
|
597
643
|
restoredTeammates: restoredTeammateSpecs,
|
|
598
644
|
includeLeadSpawnTool: true,
|
|
@@ -610,6 +656,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
610
656
|
normalized.yolo,
|
|
611
657
|
config.modelId,
|
|
612
658
|
config.toolRoutingRules,
|
|
659
|
+
config.toolPolicies,
|
|
613
660
|
skillsExecutor,
|
|
614
661
|
defaultToolExecutors,
|
|
615
662
|
)
|
|
@@ -643,7 +690,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
643
690
|
|
|
644
691
|
const completionGuard = normalized.enableAgentTeams
|
|
645
692
|
? () => {
|
|
646
|
-
const rt = this.
|
|
693
|
+
const rt = this.teamRuntimeEntries.get(registryKey)?.runtime;
|
|
647
694
|
if (!rt) return undefined;
|
|
648
695
|
const tasks = rt.listTasks();
|
|
649
696
|
const hasInProgress = tasks.some(
|
|
@@ -675,13 +722,13 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
675
722
|
: undefined;
|
|
676
723
|
|
|
677
724
|
return {
|
|
678
|
-
tools,
|
|
725
|
+
tools: filterToolsByPolicies(tools, config.toolPolicies),
|
|
679
726
|
logger: logger ?? config.logger,
|
|
680
727
|
telemetry: telemetry ?? config.telemetry,
|
|
681
728
|
teamRuntime,
|
|
682
729
|
teamRestoredFromPersistence: Boolean(restoredTeamState),
|
|
683
730
|
delegatedAgentConfigProvider:
|
|
684
|
-
this.
|
|
731
|
+
this.teamRuntimeEntries.get(registryKey)
|
|
685
732
|
?.delegatedAgentConfigProvider ?? delegatedAgentConfigProvider,
|
|
686
733
|
completionGuard,
|
|
687
734
|
registerLeadAgent: (agent) => {
|
|
@@ -692,7 +739,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
692
739
|
},
|
|
693
740
|
shutdown: async (reason: string) => {
|
|
694
741
|
shutdownTeamRuntime(teamRuntime, reason);
|
|
695
|
-
this.
|
|
742
|
+
this.teamRuntimeEntries.delete(registryKey);
|
|
696
743
|
await mcpShutdown?.();
|
|
697
744
|
if (!watcherProvided) {
|
|
698
745
|
userInstructionWatcher?.stop();
|
|
@@ -61,6 +61,22 @@ export class SubprocessSandbox {
|
|
|
61
61
|
this.options = options;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
private get processLabel(): string {
|
|
65
|
+
return this.options.name ?? "sandbox";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private clearPendingRequest(id: string): PendingRequest | undefined {
|
|
69
|
+
const pending = this.pending.get(id);
|
|
70
|
+
if (!pending) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
this.pending.delete(id);
|
|
74
|
+
if (pending.timeout) {
|
|
75
|
+
clearTimeout(pending.timeout);
|
|
76
|
+
}
|
|
77
|
+
return pending;
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
start(): void {
|
|
65
81
|
if (this.process && this.process.exitCode === null) {
|
|
66
82
|
return;
|
|
@@ -96,7 +112,7 @@ export class SubprocessSandbox {
|
|
|
96
112
|
child.on("error", (error) => {
|
|
97
113
|
this.failPending(
|
|
98
114
|
new Error(
|
|
99
|
-
`${this.
|
|
115
|
+
`${this.processLabel} process error: ${asError(error).message}`,
|
|
100
116
|
),
|
|
101
117
|
);
|
|
102
118
|
});
|
|
@@ -119,9 +135,7 @@ export class SubprocessSandbox {
|
|
|
119
135
|
this.start();
|
|
120
136
|
const child = this.process;
|
|
121
137
|
if (!child || child.exitCode !== null) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`${this.options.name ?? "sandbox"} process is not available`,
|
|
124
|
-
);
|
|
138
|
+
throw new Error(`${this.processLabel} process is not available`);
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
const id = `req_${++this.requestCounter}`;
|
|
@@ -139,13 +153,13 @@ export class SubprocessSandbox {
|
|
|
139
153
|
};
|
|
140
154
|
if ((options.timeoutMs ?? 0) > 0) {
|
|
141
155
|
pending.timeout = setTimeout(() => {
|
|
142
|
-
this.
|
|
156
|
+
this.clearPendingRequest(id);
|
|
143
157
|
this.shutdown().catch(() => {
|
|
144
158
|
// Best-effort process shutdown after timeout.
|
|
145
159
|
});
|
|
146
160
|
reject(
|
|
147
161
|
new Error(
|
|
148
|
-
`${this.
|
|
162
|
+
`${this.processLabel} call timed out after ${options.timeoutMs}ms: ${method}`,
|
|
149
163
|
),
|
|
150
164
|
);
|
|
151
165
|
}, options.timeoutMs);
|
|
@@ -155,17 +169,13 @@ export class SubprocessSandbox {
|
|
|
155
169
|
if (!error) {
|
|
156
170
|
return;
|
|
157
171
|
}
|
|
158
|
-
const entry = this.
|
|
172
|
+
const entry = this.clearPendingRequest(id);
|
|
159
173
|
if (!entry) {
|
|
160
174
|
return;
|
|
161
175
|
}
|
|
162
|
-
this.pending.delete(id);
|
|
163
|
-
if (entry.timeout) {
|
|
164
|
-
clearTimeout(entry.timeout);
|
|
165
|
-
}
|
|
166
176
|
entry.reject(
|
|
167
177
|
new Error(
|
|
168
|
-
`${this.
|
|
178
|
+
`${this.processLabel} failed to send call "${method}": ${asError(error).message}`,
|
|
169
179
|
),
|
|
170
180
|
);
|
|
171
181
|
});
|
|
@@ -176,7 +186,7 @@ export class SubprocessSandbox {
|
|
|
176
186
|
const child = this.process;
|
|
177
187
|
this.process = null;
|
|
178
188
|
if (!child || child.exitCode !== null) {
|
|
179
|
-
this.failPending(new Error(`${this.
|
|
189
|
+
this.failPending(new Error(`${this.processLabel} shutdown`));
|
|
180
190
|
return;
|
|
181
191
|
}
|
|
182
192
|
await new Promise<void>((resolve) => {
|
|
@@ -199,7 +209,7 @@ export class SubprocessSandbox {
|
|
|
199
209
|
resolve();
|
|
200
210
|
}
|
|
201
211
|
});
|
|
202
|
-
this.failPending(new Error(`${this.
|
|
212
|
+
this.failPending(new Error(`${this.processLabel} shutdown`));
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
private onMessage(
|
|
@@ -220,23 +230,16 @@ export class SubprocessSandbox {
|
|
|
220
230
|
if (message.type !== "response" || !message.id) {
|
|
221
231
|
return;
|
|
222
232
|
}
|
|
223
|
-
const pending = this.
|
|
233
|
+
const pending = this.clearPendingRequest(message.id);
|
|
224
234
|
if (!pending) {
|
|
225
235
|
return;
|
|
226
236
|
}
|
|
227
|
-
this.pending.delete(message.id);
|
|
228
|
-
if (pending.timeout) {
|
|
229
|
-
clearTimeout(pending.timeout);
|
|
230
|
-
}
|
|
231
237
|
if (message.ok) {
|
|
232
238
|
pending.resolve(message.result);
|
|
233
239
|
return;
|
|
234
240
|
}
|
|
235
241
|
pending.reject(
|
|
236
|
-
new Error(
|
|
237
|
-
message.error?.message ||
|
|
238
|
-
`${this.options.name ?? "sandbox"} call failed`,
|
|
239
|
-
),
|
|
242
|
+
new Error(message.error?.message || `${this.processLabel} call failed`),
|
|
240
243
|
);
|
|
241
244
|
}
|
|
242
245
|
|
|
@@ -18,6 +18,14 @@ function delay(ms: number): Promise<void> {
|
|
|
18
18
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
async function unlinkIfPresent(path: string): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
await unlink(path);
|
|
24
|
+
} catch {
|
|
25
|
+
// Best-effort cleanup.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export async function requestDesktopToolApproval(
|
|
22
30
|
request: ToolApprovalRequest,
|
|
23
31
|
options: DesktopToolApprovalOptions = {},
|
|
@@ -77,16 +85,10 @@ export async function requestDesktopToolApproval(
|
|
|
77
85
|
approved: parsed.approved === true,
|
|
78
86
|
reason: typeof parsed.reason === "string" ? parsed.reason : undefined,
|
|
79
87
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
await unlink(requestPath);
|
|
87
|
-
} catch {
|
|
88
|
-
// Best-effort cleanup.
|
|
89
|
-
}
|
|
88
|
+
await Promise.all([
|
|
89
|
+
unlinkIfPresent(decisionPath),
|
|
90
|
+
unlinkIfPresent(requestPath),
|
|
91
|
+
]);
|
|
90
92
|
return result;
|
|
91
93
|
} catch {
|
|
92
94
|
// Decision not available yet.
|
|
@@ -94,11 +96,7 @@ export async function requestDesktopToolApproval(
|
|
|
94
96
|
await delay(pollIntervalMs);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
await unlink(requestPath);
|
|
99
|
-
} catch {
|
|
100
|
-
// Best-effort cleanup.
|
|
101
|
-
}
|
|
99
|
+
await unlinkIfPresent(requestPath);
|
|
102
100
|
|
|
103
101
|
return { approved: false, reason: "Tool approval request timed out" };
|
|
104
102
|
}
|
|
@@ -1391,9 +1391,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
1391
1391
|
});
|
|
1392
1392
|
} catch (error) {
|
|
1393
1393
|
if (error instanceof OAuthReauthRequiredError) {
|
|
1394
|
-
throw new Error(
|
|
1395
|
-
`OAuth session for "${error.providerId}" requires re-authentication. Run "clite auth ${error.providerId}" and retry.`,
|
|
1396
|
-
);
|
|
1394
|
+
throw new Error(`${error.providerId} requires re-authentication.`);
|
|
1397
1395
|
}
|
|
1398
1396
|
throw error;
|
|
1399
1397
|
}
|
|
@@ -200,4 +200,42 @@ describe("UnifiedSessionPersistenceService", () => {
|
|
|
200
200
|
);
|
|
201
201
|
expect(row?.transcriptPath).toMatch(/\.log$/);
|
|
202
202
|
});
|
|
203
|
+
|
|
204
|
+
it("deletes the full root session directory even when artifact paths are stale", async () => {
|
|
205
|
+
const sessionsDir = mkdtempSync(join(tmpdir(), "delete-root-session-dir-"));
|
|
206
|
+
tempDirs.push(sessionsDir);
|
|
207
|
+
|
|
208
|
+
const store = new SqliteSessionStore({ sessionsDir });
|
|
209
|
+
stores.push(store);
|
|
210
|
+
const service = new CoreSessionService(store);
|
|
211
|
+
const sessionId = "root-session-delete";
|
|
212
|
+
const artifacts = await service.createRootSessionWithArtifacts({
|
|
213
|
+
sessionId,
|
|
214
|
+
source: SessionSource.CLI,
|
|
215
|
+
pid: process.pid,
|
|
216
|
+
interactive: false,
|
|
217
|
+
provider: "anthropic",
|
|
218
|
+
model: "claude-sonnet-4-6",
|
|
219
|
+
cwd: "/tmp/project",
|
|
220
|
+
workspaceRoot: "/tmp/project",
|
|
221
|
+
enableTools: true,
|
|
222
|
+
enableSpawn: false,
|
|
223
|
+
enableTeams: false,
|
|
224
|
+
prompt: "delete me",
|
|
225
|
+
startedAt: "2026-04-10T19:00:00.000Z",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
store.run(`UPDATE sessions SET messages_path = NULL WHERE session_id = ?`, [
|
|
229
|
+
sessionId,
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
expect(existsSync(artifacts.messagesPath)).toBe(true);
|
|
233
|
+
expect(existsSync(join(sessionsDir, sessionId))).toBe(true);
|
|
234
|
+
|
|
235
|
+
const result = await service.deleteSession(sessionId);
|
|
236
|
+
|
|
237
|
+
expect(result).toEqual({ deleted: true });
|
|
238
|
+
expect(existsSync(artifacts.messagesPath)).toBe(false);
|
|
239
|
+
expect(existsSync(join(sessionsDir, sessionId))).toBe(false);
|
|
240
|
+
});
|
|
203
241
|
});
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
readFileSync,
|
|
5
5
|
writeFileSync,
|
|
6
6
|
} from "node:fs";
|
|
7
|
+
import { dirname } from "node:path";
|
|
7
8
|
import type * as LlmsProviders from "@clinebot/llms";
|
|
8
9
|
import type { AgentResult } from "@clinebot/shared";
|
|
9
10
|
import { resolveRootSessionId } from "@clinebot/shared";
|
|
@@ -922,7 +923,21 @@ export class UnifiedSessionPersistenceService {
|
|
|
922
923
|
unlinkIfExists(row.hookPath);
|
|
923
924
|
unlinkIfExists(row.messagesPath);
|
|
924
925
|
unlinkIfExists(this.artifacts.sessionManifestPath(id, false));
|
|
925
|
-
|
|
926
|
+
if (row.isSubagent) {
|
|
927
|
+
this.artifacts.removeSessionDirIfEmpty(id);
|
|
928
|
+
} else {
|
|
929
|
+
const candidateDirs = new Set<string>([
|
|
930
|
+
this.artifacts.sessionArtifactsDir(id),
|
|
931
|
+
]);
|
|
932
|
+
for (const path of [row.transcriptPath, row.hookPath, row.messagesPath]) {
|
|
933
|
+
if (typeof path === "string" && path.trim().length > 0) {
|
|
934
|
+
candidateDirs.add(dirname(path));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
for (const dir of candidateDirs) {
|
|
938
|
+
this.artifacts.removeDir(dir);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
926
941
|
return { deleted: true };
|
|
927
942
|
}
|
|
928
943
|
}
|
|
@@ -234,7 +234,15 @@ export function handleAgentEvent(
|
|
|
234
234
|
|
|
235
235
|
emit({
|
|
236
236
|
type: "agent_event",
|
|
237
|
-
payload: {
|
|
237
|
+
payload: {
|
|
238
|
+
sessionId,
|
|
239
|
+
event,
|
|
240
|
+
teamAgentId: overrides?.teamAgentId,
|
|
241
|
+
teamRole:
|
|
242
|
+
overrides !== undefined
|
|
243
|
+
? (overrides.teamRole ?? (isPrimaryAgentEvent ? "lead" : undefined))
|
|
244
|
+
: undefined,
|
|
245
|
+
},
|
|
238
246
|
});
|
|
239
247
|
emit({
|
|
240
248
|
type: "chunk",
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
mkdirSync,
|
|
4
4
|
readdirSync,
|
|
5
5
|
rmdirSync,
|
|
6
|
+
rmSync,
|
|
6
7
|
unlinkSync,
|
|
7
8
|
} from "node:fs";
|
|
8
9
|
import { dirname, join } from "node:path";
|
|
@@ -116,6 +117,21 @@ export class SessionArtifacts {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
public removeSessionDir(sessionId: string): void {
|
|
121
|
+
this.removeDir(this.sessionArtifactsDir(sessionId));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public removeDir(dir: string): void {
|
|
125
|
+
if (!existsSync(dir)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
rmSync(dir, { recursive: true, force: true });
|
|
130
|
+
} catch {
|
|
131
|
+
// Best-effort cleanup.
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
public subagentArtifactPaths(
|
|
120
136
|
sessionId: string,
|
|
121
137
|
subAgentId: string,
|