428
*/
429
async materialize(ctx: IMaterializeContext): Promise<void> {
431
throw new Error('ClaudeAgentSession is already materialized');
432
}
434
>
// before it's read below; falls back to the session's `workspace` when the
435
>
// host didn't resolve a dedicated directory.
436
>
if (ctx.workingDirectory && !isEqual(ctx.workingDirectory, this.workingDirectory)) {
437
this._workingDirectory = ctx.workingDirectory;
438
this._watchCustomizations(ctx.workingDirectory);
439
}
441
throw new Error(`Cannot materialize Claude session ${this.sessionId}: workingDirectory is required`);
442
}
444
>
445
>
const permissionMode = readClaudePermissionMode(this._configurationService, this._storageUri) ?? this._permissionModeFallback;
446
>
const { mcpServers, allowedTools } = await this._buildStartupToolWiring(ctx.serverToolHost);
447
>
const agentName = await resolveClaudeAgentName(this._provisionalAgent, this._fileService, this._logService, this.sessionId);
448
>
449
>
const options = await buildOptions(
450
>
{
451
>
sessionId: this.sessionId,
452
>
workingDirectory: this.workingDirectory,
453
>
model: this._provisionalModel,
454
>
abortController: this.abortController,
455
>
permissionMode,
456
>
canUseTool: ctx.canUseTool,
457
>
onElicitation: ctx.onElicitation,
458
>
isResume: ctx.isResume,
459
>
resumeSessionAt: this._pendingResumeSessionAt,
460
>
mcpServers,
461
>
allowedTools,
462
>
plugins: this.clientCustomizationsDiff.consume(this._desiredClientPluginPaths()),
463
>
agent: agentName,
464
>
},
465
>
ctx.transport,
466
>
data => this._logService.error(`[Claude SDK stderr] ${data}`),
467
>
);
468
>
469
>
this._logService.info(`[Claude] session ${this.sessionId}: enableFileCheckpointing=${options.enableFileCheckpointing} isResume=${ctx.isResume}`);
470
>
471
>
const warm = await this._sdkService.startup({ options });
472
>
473
>
if (this.abortController.signal.aborted) {
474
await warm[Symbol.asyncDispose]();
475
throw new CancellationError();
476
}
478
>
const dbRef = this._sessionDataService.openDatabase(this._storageUri);
479
>
let pipeline: ClaudeSdkPipeline;
480
>
try {
481
>
pipeline = this._register(this._instantiationService.createInstance(
482
>
ClaudeSdkPipeline,
483
>
this.sessionId,
484
>
this.sessionUri,
485
>
this._chatChannelUri,
486
>
warm,
487
>
this.abortController,
488
>
dbRef,
489
>
this.subagents,
490
>
(toolName: string) => this.toolDiff.model.ownerOf(toolName),
491
>
));
492
>
} catch (err) {
493
dbRef.dispose();
494
await warm[Symbol.asyncDispose]();
495
throw err;
496
}
497
>
this._register(pipeline.onDidProduceSignal(s => this._onDidSessionProgress.fire(this._enrichSignalWithMcpContributor(this._enrichSignalWithCredits(s)))));
claudeAgentSession.ts
498
>
this._pipeline = pipeline;
499
>
// The materialize succeeded with the staged anchor applied to `Options`
500
>
// — clear it now so it isn't re-applied. A throw before this point (e.g.
501
>
// `startup` / pipeline-create) leaves it staged for the next retry.
502
>
this._pendingResumeSessionAt = undefined;
503
>
504
>
// Seed the pipeline's bijective config cache so a rebuild re-applies
505
>
// the user's last-chosen model / effort without losing the picker
506
>
// config. Read provisional state directly off the session.
507
>
pipeline.seedCurrentConfig(
508
>
toSdkModelId(this._provisionalModel?.id),
509
>
toRuntimeEffortLevel(resolveClaudeEffort(this._provisionalModel)),
510
>
permissionMode,
511
>
);
512
>
513
>
// Fresh sessions persist their customization-directory / model /
514
>
// permissionMode overlay so a later resume re-reads them. Resume
515
>
// sessions skip the write because they READ from the overlay
516
>
// upstream and would otherwise overwrite their source.
517
>
if (!ctx.isResume) {
518
try {
519
await this._metadataStore.write(this._storageUri, {