praxis-agent 0.26.0 → 0.28.0
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/application/session-memory.d.ts +43 -10
- package/dist/application/session-memory.js +263 -62
- package/dist/application/session-service.d.ts +13 -4
- package/dist/application/session-service.js +139 -36
- package/dist/application/subagent-service.d.ts +1 -0
- package/dist/application/subagent-service.js +23 -1
- package/dist/cli-runtime.d.ts +4 -0
- package/dist/cli-runtime.js +10 -0
- package/dist/core/runtime.d.ts +10 -0
- package/dist/core/runtime.js +111 -28
- package/dist/core/tool-execution-scheduler.d.ts +38 -0
- package/dist/core/tool-execution-scheduler.js +95 -0
- package/dist/core/tool-scheduling-policy.d.ts +4 -0
- package/dist/core/tool-scheduling-policy.js +24 -0
- package/dist/extensions/claude-extension-tools.d.ts +1 -0
- package/dist/extensions/claude-extension-tools.js +6 -0
- package/dist/hooks/claude-hook-tools.d.ts +4 -0
- package/dist/hooks/claude-hook-tools.js +6 -0
- package/dist/mcp/claude-mcp-tools.d.ts +1 -0
- package/dist/mcp/claude-mcp-tools.js +42 -0
- package/dist/permissions/claude-permission-resolver.js +3 -3
- package/dist/permissions/permission-updates.d.ts +1 -0
- package/dist/permissions/permission-updates.js +13 -1
- package/dist/tools/claude-capabilities.d.ts +1 -0
- package/dist/tools/claude-capabilities.js +5 -0
- package/dist/tools/claude-interactive-tools.js +7 -0
- package/dist/tools/claude-lsp-tool.js +7 -0
- package/dist/tools/claude-scheduled-tools.d.ts +1 -0
- package/dist/tools/claude-scheduled-tools.js +7 -0
- package/dist/tools/claude-task-tools.d.ts +1 -0
- package/dist/tools/claude-task-tools.js +11 -0
- package/dist/tools/claude-user-message.d.ts +1 -0
- package/dist/tools/claude-user-message.js +7 -0
- package/dist/tools/claude-workflow-tools.d.ts +1 -0
- package/dist/tools/claude-workflow-tools.js +7 -0
- package/dist/tools/claude-worktree-tools.d.ts +1 -0
- package/dist/tools/claude-worktree-tools.js +7 -0
- package/dist/tools/filtered-tool-registry.d.ts +1 -0
- package/dist/tools/filtered-tool-registry.js +5 -0
- package/dist/tools/local-tools.d.ts +17 -0
- package/dist/tools/local-tools.js +86 -1
- package/dist/tools/web.d.ts +1 -0
- package/dist/tools/web.js +27 -0
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ import { selectClaudeSchemaAdapter, } from '../compatibility/claude/schema.js';
|
|
|
16
16
|
import { findUnresolvedClaudeToolCalls, getClaudeContentBlocks, } from '../compatibility/claude/tool-links.js';
|
|
17
17
|
import { createClaudeAgentSettingEntry, createClaudeHookAttachmentEntries, createClaudeLastPromptEntry, createClaudeRuleAttachmentEntry, translateProviderEvents, } from '../compatibility/claude/translation.js';
|
|
18
18
|
import { AgentRunCancelledError, AgentRuntime, } from '../core/runtime.js';
|
|
19
|
+
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
19
20
|
import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
20
21
|
import { usageCostUsd } from '../core/usage.js';
|
|
21
22
|
import { ContextBudget, ContextRecoveryPlanner, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
@@ -61,6 +62,7 @@ function mainAgentToolNames(tools, agent) {
|
|
|
61
62
|
}
|
|
62
63
|
const emptyToolRegistry = {
|
|
63
64
|
definitions: () => [],
|
|
65
|
+
schedulingPolicy: () => ({ concurrency: 'exclusive' }),
|
|
64
66
|
prepare: async (call) => call,
|
|
65
67
|
execute: async () => ({ content: '', isError: false }),
|
|
66
68
|
};
|
|
@@ -307,10 +309,10 @@ Return ONLY an updated Markdown document that becomes the session's durable memo
|
|
|
307
309
|
* branch can provide them. Larger suffixes stay conservative. */
|
|
308
310
|
const MEMORY_COMPACT_MIN_SUFFIX_MESSAGES = 5;
|
|
309
311
|
const MEMORY_COMPACT_MIN_SUFFIX_TOKENS = 10_000;
|
|
310
|
-
/** Above this estimated
|
|
311
|
-
*
|
|
312
|
-
* projection. */
|
|
312
|
+
/** Above this estimated post-compact envelope the selective path falls back to
|
|
313
|
+
* full compaction instead of retaining an oversized active projection. */
|
|
313
314
|
const MEMORY_COMPACT_MAX_PROJECTION_TOKENS = 40_000;
|
|
315
|
+
const MEMORY_COMPACT_MAX_SUMMARY_TOKENS = 8_192;
|
|
314
316
|
function isTextBearingClaudeEntry(entry) {
|
|
315
317
|
if (typeof entry.message !== 'object' ||
|
|
316
318
|
entry.message === null ||
|
|
@@ -360,12 +362,38 @@ function claudeAssistantHasToolUse(entry, ids) {
|
|
|
360
362
|
typeof block.id === 'string' &&
|
|
361
363
|
ids.has(block.id));
|
|
362
364
|
}
|
|
365
|
+
function claudeAssistantResponseId(entry) {
|
|
366
|
+
if (entry?.type !== 'assistant' ||
|
|
367
|
+
typeof entry.message !== 'object' ||
|
|
368
|
+
entry.message === null ||
|
|
369
|
+
Array.isArray(entry.message)) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const id = entry.message.id;
|
|
373
|
+
return typeof id === 'string' && id.length > 0 ? id : null;
|
|
374
|
+
}
|
|
375
|
+
/** Claude transcripts may normalize one provider response into consecutive
|
|
376
|
+
* assistant records with the same message.id. Keep the whole response on one
|
|
377
|
+
* side of the compact boundary so thinking and tool siblings stay together. */
|
|
378
|
+
function completeClaudeAssistantResponseBoundary(activeEntries, startIndex, lowerBound) {
|
|
379
|
+
const responseId = claudeAssistantResponseId(activeEntries[startIndex]);
|
|
380
|
+
if (responseId === null)
|
|
381
|
+
return startIndex;
|
|
382
|
+
let boundary = startIndex;
|
|
383
|
+
for (let index = startIndex - 1; index >= lowerBound; index -= 1) {
|
|
384
|
+
const candidate = activeEntries[index];
|
|
385
|
+
if (claudeAssistantResponseId(candidate) !== responseId)
|
|
386
|
+
break;
|
|
387
|
+
boundary = index;
|
|
388
|
+
}
|
|
389
|
+
return boundary;
|
|
390
|
+
}
|
|
363
391
|
/** Pulls the preserved-suffix boundary backward so a tool_use assistant entry
|
|
364
392
|
* and its tool_result user entry stay siblings: the suffix never opens with an
|
|
365
393
|
* orphaned tool_result and the compacted input never ends with a dangling
|
|
366
394
|
* tool_use. Same-response thinking/tool blocks live in one assistant entry,
|
|
367
395
|
* so entry-level cutting never splits them. */
|
|
368
|
-
function completeClaudeToolPairBoundary(activeEntries, startIndex) {
|
|
396
|
+
function completeClaudeToolPairBoundary(activeEntries, startIndex, lowerBound) {
|
|
369
397
|
let boundary = startIndex;
|
|
370
398
|
while (boundary < activeEntries.length) {
|
|
371
399
|
const entry = activeEntries[boundary];
|
|
@@ -375,7 +403,7 @@ function completeClaudeToolPairBoundary(activeEntries, startIndex) {
|
|
|
375
403
|
if (ids.size === 0)
|
|
376
404
|
break;
|
|
377
405
|
let extended = false;
|
|
378
|
-
for (let index = boundary - 1; index >=
|
|
406
|
+
for (let index = boundary - 1; index >= lowerBound; index -= 1) {
|
|
379
407
|
const candidate = activeEntries[index];
|
|
380
408
|
if (candidate && claudeAssistantHasToolUse(candidate, ids)) {
|
|
381
409
|
boundary = index;
|
|
@@ -390,11 +418,18 @@ function completeClaudeToolPairBoundary(activeEntries, startIndex) {
|
|
|
390
418
|
}
|
|
391
419
|
/** Walks back from the active tail to the inclusive start of the recent suffix
|
|
392
420
|
* retained verbatim, then completes sibling adjacency at the boundary. */
|
|
393
|
-
function memoryPreservedSuffixStart(activeEntries) {
|
|
421
|
+
export function memoryPreservedSuffixStart(activeEntries) {
|
|
422
|
+
let latestCompactBoundary = 0;
|
|
423
|
+
for (let index = activeEntries.length - 1; index >= 0; index -= 1) {
|
|
424
|
+
if (activeEntries[index]?.isCompactSummary === true) {
|
|
425
|
+
latestCompactBoundary = index;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
394
429
|
let start = activeEntries.length;
|
|
395
430
|
let textBearing = 0;
|
|
396
431
|
let estimatedTokens = 0;
|
|
397
|
-
for (let index = activeEntries.length - 1; index >=
|
|
432
|
+
for (let index = activeEntries.length - 1; index >= latestCompactBoundary; index -= 1) {
|
|
398
433
|
const entry = activeEntries[index];
|
|
399
434
|
if (!entry)
|
|
400
435
|
continue;
|
|
@@ -404,11 +439,13 @@ function memoryPreservedSuffixStart(activeEntries) {
|
|
|
404
439
|
textBearing += 1;
|
|
405
440
|
estimatedTokens += estimateModelRequestTokens(projectClaudeModelMessages([entry]));
|
|
406
441
|
if (textBearing >= MEMORY_COMPACT_MIN_SUFFIX_MESSAGES &&
|
|
407
|
-
(estimatedTokens >= MEMORY_COMPACT_MIN_SUFFIX_TOKENS ||
|
|
442
|
+
(estimatedTokens >= MEMORY_COMPACT_MIN_SUFFIX_TOKENS ||
|
|
443
|
+
index === latestCompactBoundary)) {
|
|
408
444
|
break;
|
|
409
445
|
}
|
|
410
446
|
}
|
|
411
|
-
|
|
447
|
+
const toolSafeStart = completeClaudeToolPairBoundary(activeEntries, start, latestCompactBoundary);
|
|
448
|
+
return completeClaudeAssistantResponseBoundary(activeEntries, toolSafeStart, latestCompactBoundary);
|
|
412
449
|
}
|
|
413
450
|
function validPromptSuggestion(value) {
|
|
414
451
|
const suggestion = value.trim();
|
|
@@ -558,6 +595,7 @@ export class ClaudeSessionService {
|
|
|
558
595
|
activeCostSessionId;
|
|
559
596
|
closeCostSavePromise;
|
|
560
597
|
sessionMemoryControllers = new Map();
|
|
598
|
+
resolvedSessionMemoryProvider;
|
|
561
599
|
hookLifecycle;
|
|
562
600
|
runtimeCwd;
|
|
563
601
|
constructor(options) {
|
|
@@ -708,6 +746,7 @@ export class ClaudeSessionService {
|
|
|
708
746
|
await Promise.all([...this.backgroundNotificationWrites.values()]);
|
|
709
747
|
this.hostedSubagents.clear();
|
|
710
748
|
this.backgroundTasks.clear();
|
|
749
|
+
await Promise.all([...this.sessionMemoryControllers.values()].map((controller) => controller.close()));
|
|
711
750
|
this.sessionMemoryControllers.clear();
|
|
712
751
|
await this.workflowManager?.close();
|
|
713
752
|
this.closeCostSavePromise ??= this.persistActiveSessionCost();
|
|
@@ -906,6 +945,7 @@ export class ClaudeSessionService {
|
|
|
906
945
|
(rightIndex < 0 ? preferredOrder.length : rightIndex));
|
|
907
946
|
});
|
|
908
947
|
},
|
|
948
|
+
schedulingPolicy: (call) => resolveToolSchedulingPolicy(capabilityRegistry, call),
|
|
909
949
|
prepare: (call, context) => capabilityRegistry.prepare(call, context),
|
|
910
950
|
execute: (call, context) => capabilityRegistry.execute(call, context),
|
|
911
951
|
};
|
|
@@ -2279,6 +2319,10 @@ export class ClaudeSessionService {
|
|
|
2279
2319
|
const fileHistoryTools = fileHistory && interactiveMessageTools
|
|
2280
2320
|
? {
|
|
2281
2321
|
definitions: () => interactiveMessageTools.definitions(),
|
|
2322
|
+
schedulingPolicy: (call) => ({
|
|
2323
|
+
...resolveToolSchedulingPolicy(interactiveMessageTools, call),
|
|
2324
|
+
startAfterAssistant: true,
|
|
2325
|
+
}),
|
|
2282
2326
|
prepare: (call, context) => interactiveMessageTools.prepare(call, context),
|
|
2283
2327
|
execute: async (call, context) => {
|
|
2284
2328
|
const path = call.name === 'Write' || call.name === 'Edit'
|
|
@@ -2685,7 +2729,7 @@ export class ClaudeSessionService {
|
|
|
2685
2729
|
];
|
|
2686
2730
|
const irreducible = budget.evaluate(irreducibleMessages, definitions);
|
|
2687
2731
|
budget.assertFits(irreducible);
|
|
2688
|
-
|
|
2732
|
+
let logicalParentUuid = compactionAnchorUuid;
|
|
2689
2733
|
if (!logicalParentUuid || historyMessages.length === 0) {
|
|
2690
2734
|
budget.assertFits(predicted);
|
|
2691
2735
|
throw new Error('Cannot compact an empty Claude transcript');
|
|
@@ -2693,8 +2737,17 @@ export class ClaudeSessionService {
|
|
|
2693
2737
|
if (findUnresolvedClaudeToolCalls(snapshot.entries).length > 0) {
|
|
2694
2738
|
throw new Error('Cannot compact a Claude session with unresolved tool calls');
|
|
2695
2739
|
}
|
|
2696
|
-
|
|
2697
|
-
await
|
|
2740
|
+
const memorySelection = sessionMemory
|
|
2741
|
+
? await this.selectMemoryPreservedCompact(sessionId, selectClaudeActiveTranscript(snapshot.entries))
|
|
2742
|
+
: null;
|
|
2743
|
+
const compactorMessages = memorySelection
|
|
2744
|
+
? [
|
|
2745
|
+
memorySelection.memoryMessage,
|
|
2746
|
+
...projectClaudeModelMessages(memorySelection.compactedEntries),
|
|
2747
|
+
]
|
|
2748
|
+
: historyMessages;
|
|
2749
|
+
if (memorySelection) {
|
|
2750
|
+
logicalParentUuid = memorySelection.logicalParentUuid;
|
|
2698
2751
|
}
|
|
2699
2752
|
this.options.eventSink?.({ type: 'state', state: 'compacting' });
|
|
2700
2753
|
const compactEnvelope = budget.evaluate([
|
|
@@ -2716,7 +2769,7 @@ export class ClaudeSessionService {
|
|
|
2716
2769
|
targetTokens = 1;
|
|
2717
2770
|
}
|
|
2718
2771
|
const compacted = await (this.options.compactor ?? new ModelCompactor(provider)).compact({
|
|
2719
|
-
messages:
|
|
2772
|
+
messages: compactorMessages,
|
|
2720
2773
|
targetTokens,
|
|
2721
2774
|
contextWindowTokens: budget.contextWindowTokens,
|
|
2722
2775
|
...(signal ? { signal } : {}),
|
|
@@ -2748,6 +2801,11 @@ export class ClaudeSessionService {
|
|
|
2748
2801
|
cwd: this.activeCwd(),
|
|
2749
2802
|
claudeVersion: this.options.claudeVersion,
|
|
2750
2803
|
gitBranch: null,
|
|
2804
|
+
...(memorySelection
|
|
2805
|
+
? {
|
|
2806
|
+
preservedUuids: memorySelection.preservedEntries.flatMap((entry) => typeof entry.uuid === 'string' ? [entry.uuid] : []),
|
|
2807
|
+
}
|
|
2808
|
+
: {}),
|
|
2751
2809
|
createUuid: () => uuids.shift() ?? randomUUID(),
|
|
2752
2810
|
now: () => timestamp,
|
|
2753
2811
|
});
|
|
@@ -3032,7 +3090,7 @@ export class ClaudeSessionService {
|
|
|
3032
3090
|
toolResultDirectory,
|
|
3033
3091
|
observer,
|
|
3034
3092
|
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
3035
|
-
...(this.options.maxModelTurns
|
|
3093
|
+
...(this.options.maxModelTurns !== undefined
|
|
3036
3094
|
? { maxModelTurns: this.options.maxModelTurns }
|
|
3037
3095
|
: {}),
|
|
3038
3096
|
...(this.options.betas?.length ? { betas: this.options.betas } : {}),
|
|
@@ -3270,21 +3328,25 @@ export class ClaudeSessionService {
|
|
|
3270
3328
|
});
|
|
3271
3329
|
}
|
|
3272
3330
|
if (sessionMemory && finalLeafUuid) {
|
|
3273
|
-
const
|
|
3274
|
-
const
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3331
|
+
const memorySnapshot = projectClaudeModelMessages(snapshot.entries);
|
|
3332
|
+
const providerVisibleMessages = [
|
|
3333
|
+
...contextMessages,
|
|
3334
|
+
...injectTurnContext(memorySnapshot),
|
|
3335
|
+
];
|
|
3336
|
+
const currentContextTokens = budget
|
|
3337
|
+
? budget.evaluate(providerVisibleMessages, definitions)
|
|
3338
|
+
.occupancyTokens
|
|
3339
|
+
: estimateModelRequestTokens(providerVisibleMessages, definitions);
|
|
3278
3340
|
try {
|
|
3279
|
-
await sessionMemory.
|
|
3280
|
-
// Normal turns schedule extraction without awaiting it; failures
|
|
3281
|
-
// surface as a warning while the sidecar stays retryable.
|
|
3282
|
-
sessionMemory.waitForIdle().catch(warn);
|
|
3341
|
+
await sessionMemory.observeContext(currentContextTokens, currentTurnToolCalls, finalLeafUuid, memorySnapshot);
|
|
3283
3342
|
}
|
|
3284
3343
|
catch (error) {
|
|
3285
3344
|
// A failed observation must not fail the user turn; the sidecar
|
|
3286
3345
|
// retains a retryable error for the next observation.
|
|
3287
|
-
|
|
3346
|
+
this.options.eventSink?.({
|
|
3347
|
+
type: 'warning',
|
|
3348
|
+
message: `Session memory observation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3349
|
+
});
|
|
3288
3350
|
}
|
|
3289
3351
|
}
|
|
3290
3352
|
turnCompleted = true;
|
|
@@ -3402,7 +3464,7 @@ export class ClaudeSessionService {
|
|
|
3402
3464
|
return agentName;
|
|
3403
3465
|
return null;
|
|
3404
3466
|
}
|
|
3405
|
-
/** Conservative selective-preservation seam for
|
|
3467
|
+
/** Conservative selective-preservation seam for full compact: when the
|
|
3406
3468
|
* durable session memory watermark matches an active entry, summarize the
|
|
3407
3469
|
* last good memory artifact plus the post-watermark branch and retain a
|
|
3408
3470
|
* recent suffix. Returns null to fall back to the existing full-compaction
|
|
@@ -3412,6 +3474,7 @@ export class ClaudeSessionService {
|
|
|
3412
3474
|
const controller = this.sessionMemoryController(sessionId);
|
|
3413
3475
|
if (controller === null)
|
|
3414
3476
|
return null;
|
|
3477
|
+
await controller.waitForCompact();
|
|
3415
3478
|
let watermark = null;
|
|
3416
3479
|
let memorySummary = '';
|
|
3417
3480
|
try {
|
|
@@ -3437,11 +3500,18 @@ export class ClaudeSessionService {
|
|
|
3437
3500
|
return null;
|
|
3438
3501
|
const compactedEntries = activeEntries.slice(watermarkIndex + 1, suffixStart);
|
|
3439
3502
|
const preservedEntries = activeEntries.slice(suffixStart);
|
|
3440
|
-
const
|
|
3441
|
-
if (
|
|
3503
|
+
const preservedMessages = projectClaudeModelMessages(preservedEntries);
|
|
3504
|
+
if (projectClaudeModelMessages(compactedEntries).length === 0)
|
|
3442
3505
|
return null;
|
|
3443
3506
|
const memoryMessage = { role: 'user', content: memorySummary };
|
|
3444
|
-
if (estimateModelRequestTokens([
|
|
3507
|
+
if (estimateModelRequestTokens([
|
|
3508
|
+
{
|
|
3509
|
+
role: 'user',
|
|
3510
|
+
content: formatClaudeCompactSummary(''),
|
|
3511
|
+
},
|
|
3512
|
+
...preservedMessages,
|
|
3513
|
+
]) +
|
|
3514
|
+
MEMORY_COMPACT_MAX_SUMMARY_TOKENS >
|
|
3445
3515
|
MEMORY_COMPACT_MAX_PROJECTION_TOKENS) {
|
|
3446
3516
|
return null;
|
|
3447
3517
|
}
|
|
@@ -4045,7 +4115,9 @@ export class ClaudeSessionService {
|
|
|
4045
4115
|
return false;
|
|
4046
4116
|
if (this.options.sessionPersistence === false)
|
|
4047
4117
|
return false;
|
|
4048
|
-
|
|
4118
|
+
if (this.options.sessionKind === 'bg')
|
|
4119
|
+
return false;
|
|
4120
|
+
return this.sessionMemoryProviderFactory() !== null;
|
|
4049
4121
|
}
|
|
4050
4122
|
sessionMemoryController(sessionId) {
|
|
4051
4123
|
if (!this.sessionMemoryEnabled() || !isClaudeSessionId(sessionId)) {
|
|
@@ -4059,7 +4131,11 @@ export class ClaudeSessionService {
|
|
|
4059
4131
|
sessionId,
|
|
4060
4132
|
sidecarRoot: this.paths(sessionId).praxisRoot,
|
|
4061
4133
|
}),
|
|
4062
|
-
extractor: (input) => this.extractSessionMemory(
|
|
4134
|
+
extractor: (input) => this.extractSessionMemory(input),
|
|
4135
|
+
onExtractionError: (error) => this.options.eventSink?.({
|
|
4136
|
+
type: 'warning',
|
|
4137
|
+
message: `Session memory extraction failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
4138
|
+
}),
|
|
4063
4139
|
});
|
|
4064
4140
|
this.sessionMemoryControllers.set(sessionId, controller);
|
|
4065
4141
|
}
|
|
@@ -4110,10 +4186,16 @@ export class ClaudeSessionService {
|
|
|
4110
4186
|
parts.push(`Tool result: ${message.content}`);
|
|
4111
4187
|
}
|
|
4112
4188
|
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4189
|
+
// The successful watermark names the newest message in this snapshot, so
|
|
4190
|
+
// extraction must receive every message in full. Summary injection remains
|
|
4191
|
+
// bounded separately; provider refusal leaves the old watermark retryable.
|
|
4192
|
+
return parts.join('\n\n');
|
|
4193
|
+
}
|
|
4194
|
+
async extractSessionMemory(input) {
|
|
4195
|
+
const provider = this.sessionMemoryProvider();
|
|
4196
|
+
if (provider === null) {
|
|
4197
|
+
throw new SessionMemoryStateError('Session memory requires an isolated provider factory');
|
|
4198
|
+
}
|
|
4117
4199
|
const messages = [
|
|
4118
4200
|
{ role: 'system', content: SESSION_MEMORY_EXTRACTION_PROMPT },
|
|
4119
4201
|
...(input.summary.trim().length === 0
|
|
@@ -4133,8 +4215,11 @@ export class ClaudeSessionService {
|
|
|
4133
4215
|
},
|
|
4134
4216
|
]),
|
|
4135
4217
|
];
|
|
4136
|
-
|
|
4137
|
-
|
|
4218
|
+
// Session memory is operational side work: it never contributes to the
|
|
4219
|
+
// foreground result or the persisted session cost/usage tracker.
|
|
4220
|
+
const metrics = await completeMeteredModelRequest(provider, {
|
|
4221
|
+
messages,
|
|
4222
|
+
signal: input.signal,
|
|
4138
4223
|
});
|
|
4139
4224
|
if (metrics.toolCalls.length > 0) {
|
|
4140
4225
|
throw new SessionMemoryStateError('Session memory extraction must not call tools');
|
|
@@ -4145,6 +4230,24 @@ export class ClaudeSessionService {
|
|
|
4145
4230
|
}
|
|
4146
4231
|
return summary;
|
|
4147
4232
|
}
|
|
4233
|
+
sessionMemoryProvider() {
|
|
4234
|
+
if (this.resolvedSessionMemoryProvider !== undefined) {
|
|
4235
|
+
return this.resolvedSessionMemoryProvider;
|
|
4236
|
+
}
|
|
4237
|
+
const factory = this.sessionMemoryProviderFactory();
|
|
4238
|
+
this.resolvedSessionMemoryProvider = factory?.() ?? null;
|
|
4239
|
+
return this.resolvedSessionMemoryProvider;
|
|
4240
|
+
}
|
|
4241
|
+
sessionMemoryProviderFactory() {
|
|
4242
|
+
if (this.options.sessionMemoryProviderFactory) {
|
|
4243
|
+
return this.options.sessionMemoryProviderFactory;
|
|
4244
|
+
}
|
|
4245
|
+
const selectProvider = this.options.providerForMainModel ?? this.options.providerForModel;
|
|
4246
|
+
const foregroundModel = this.options.provider?.model;
|
|
4247
|
+
return foregroundModel && selectProvider
|
|
4248
|
+
? () => selectProvider(foregroundModel)
|
|
4249
|
+
: null;
|
|
4250
|
+
}
|
|
4148
4251
|
toolCapabilities() {
|
|
4149
4252
|
const taskNames = this.options.taskToolNames ?? [];
|
|
4150
4253
|
const input = {
|
|
@@ -45,6 +45,7 @@ export declare class StructuredOutputRegistry implements ToolRegistry {
|
|
|
45
45
|
value: unknown;
|
|
46
46
|
} | undefined);
|
|
47
47
|
definitions(): readonly ModelToolDefinition[];
|
|
48
|
+
schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
|
|
48
49
|
prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
|
|
49
50
|
execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
50
51
|
}
|
|
@@ -11,6 +11,7 @@ import { createClaudeHookAttachmentEntries, translateProviderEvents, } from '../
|
|
|
11
11
|
import { injectFirstUserMessageContext, } from '../core/context.js';
|
|
12
12
|
import { ContextBudget } from '../core/context-budget.js';
|
|
13
13
|
import { AgentRuntime, } from '../core/runtime.js';
|
|
14
|
+
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
14
15
|
import { BUILTIN_STATUSLINE_AGENT_PATH, } from '../extensions/claude-extensions.js';
|
|
15
16
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
16
17
|
import { ClaudeSidechainStore } from '../persistence/claude-sidechain-store.js';
|
|
@@ -26,6 +27,7 @@ const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
|
26
27
|
const SIDECHAIN_DISCOVERY_MAX_DEPTH = 4;
|
|
27
28
|
const structuredOnlyTools = {
|
|
28
29
|
definitions: () => [],
|
|
30
|
+
schedulingPolicy: () => ({ concurrency: 'exclusive' }),
|
|
29
31
|
prepare: async (call) => call,
|
|
30
32
|
execute: async () => ({ content: '', isError: false }),
|
|
31
33
|
};
|
|
@@ -68,6 +70,12 @@ export class StructuredOutputRegistry {
|
|
|
68
70
|
]),
|
|
69
71
|
];
|
|
70
72
|
}
|
|
73
|
+
schedulingPolicy(call) {
|
|
74
|
+
if (call.name === 'StructuredOutput') {
|
|
75
|
+
return { concurrency: 'exclusive' };
|
|
76
|
+
}
|
|
77
|
+
return resolveToolSchedulingPolicy(this.base, call);
|
|
78
|
+
}
|
|
71
79
|
prepare(call, context) {
|
|
72
80
|
if (call.name !== 'StructuredOutput')
|
|
73
81
|
return this.base.prepare(call, context);
|
|
@@ -106,6 +114,12 @@ class RestrictedToolRegistry {
|
|
|
106
114
|
.definitions()
|
|
107
115
|
.filter((definition) => this.allowed.has(definition.name));
|
|
108
116
|
}
|
|
117
|
+
schedulingPolicy(call) {
|
|
118
|
+
if (!this.allowed.has(call.name)) {
|
|
119
|
+
return { concurrency: 'exclusive' };
|
|
120
|
+
}
|
|
121
|
+
return resolveToolSchedulingPolicy(this.base, call);
|
|
122
|
+
}
|
|
109
123
|
prepare(call, context) {
|
|
110
124
|
if (!this.allowed.has(call.name))
|
|
111
125
|
throw new Error(`Tool ${call.name} is unavailable to this agent`);
|
|
@@ -1353,7 +1367,9 @@ export class ClaudeSubagentExecutor {
|
|
|
1353
1367
|
const runtime = new AgentRuntime(options.provider, emit, {
|
|
1354
1368
|
tools: runtimeTools,
|
|
1355
1369
|
permissions: runtimePermissions,
|
|
1356
|
-
|
|
1370
|
+
...(customAgent?.maxTurns === undefined
|
|
1371
|
+
? {}
|
|
1372
|
+
: { maxModelTurns: customAgent.maxTurns }),
|
|
1357
1373
|
maxModelOutputBytes: this.options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
|
|
1358
1374
|
maxToolCallsPerTurn: 32,
|
|
1359
1375
|
maxToolInputBytes: 1024 * 1024,
|
|
@@ -1647,6 +1663,12 @@ class ClaudeSubagentToolRegistry {
|
|
|
1647
1663
|
...ordinary.slice(insertionIndex),
|
|
1648
1664
|
];
|
|
1649
1665
|
}
|
|
1666
|
+
schedulingPolicy(call) {
|
|
1667
|
+
if (['Agent', 'SendMessage', 'TaskOutput', 'TaskStop'].includes(call.name)) {
|
|
1668
|
+
return { concurrency: 'exclusive', cancelOnInterrupt: true };
|
|
1669
|
+
}
|
|
1670
|
+
return resolveToolSchedulingPolicy(this.base, call);
|
|
1671
|
+
}
|
|
1650
1672
|
async prepare(call, context) {
|
|
1651
1673
|
if (call.name === 'Agent')
|
|
1652
1674
|
return this.executor.prepare(call, this.depth);
|
package/dist/cli-runtime.d.ts
CHANGED
|
@@ -178,6 +178,10 @@ export interface CliDependencies extends InteractiveServiceFactory {
|
|
|
178
178
|
* to the existing default behavior (undefined).
|
|
179
179
|
*/
|
|
180
180
|
export declare function resolveRuntimeModel(explicitModel: string | undefined, environment: NodeJS.ProcessEnv, settings: PraxisRuntimeSettings | undefined): string | undefined;
|
|
181
|
+
/** Builds the default CLI's isolated Session-memory adapter seam. Each
|
|
182
|
+
* invocation reconstructs the selected main-model provider stack instead of
|
|
183
|
+
* sharing foreground adapter-local cache and retry state. */
|
|
184
|
+
export declare function createSessionMemoryProviderFactory(providerForMainModel: ((model: string) => ModelProvider) | undefined, model: string | undefined): (() => ModelProvider) | undefined;
|
|
181
185
|
export declare function resolveInteractiveRuntimeSettingsLocation(dataPlane: DataPlane, environment?: NodeJS.ProcessEnv): {
|
|
182
186
|
configRoot: string;
|
|
183
187
|
statePath: string;
|
package/dist/cli-runtime.js
CHANGED
|
@@ -749,6 +749,14 @@ export function resolveRuntimeModel(explicitModel, environment, settings) {
|
|
|
749
749
|
(envModel !== undefined && envModel.trim() !== '' ? envModel : undefined) ??
|
|
750
750
|
settingsModel);
|
|
751
751
|
}
|
|
752
|
+
/** Builds the default CLI's isolated Session-memory adapter seam. Each
|
|
753
|
+
* invocation reconstructs the selected main-model provider stack instead of
|
|
754
|
+
* sharing foreground adapter-local cache and retry state. */
|
|
755
|
+
export function createSessionMemoryProviderFactory(providerForMainModel, model) {
|
|
756
|
+
if (!providerForMainModel || !model)
|
|
757
|
+
return undefined;
|
|
758
|
+
return () => providerForMainModel(model);
|
|
759
|
+
}
|
|
752
760
|
export function resolveInteractiveRuntimeSettingsLocation(dataPlane, environment = process.env) {
|
|
753
761
|
const configRoot = resolveDataPlaneRoot({ dataPlane, environment });
|
|
754
762
|
return {
|
|
@@ -854,6 +862,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
854
862
|
projectIdentity: await resolveClaudeProjectIdentity({ cwd }),
|
|
855
863
|
sidecarPath: resolveUnknownCostSidecarPath(dataPlane, configRoot),
|
|
856
864
|
});
|
|
865
|
+
const sessionMemoryProviderFactory = createSessionMemoryProviderFactory(providerForMainModel, model);
|
|
857
866
|
const options = {
|
|
858
867
|
configRoot,
|
|
859
868
|
dataPlane,
|
|
@@ -879,6 +888,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
879
888
|
: { maxBudgetUsd: cli.maxBudgetUsd }),
|
|
880
889
|
pricing: ModelPricingRegistry.fromEnvironment(runtimeEnvironment.PRAXIS_PRICING_JSON),
|
|
881
890
|
collectMetrics: true,
|
|
891
|
+
...(sessionMemoryProviderFactory ? { sessionMemoryProviderFactory } : {}),
|
|
882
892
|
...(sessionKind === undefined ? {} : { sessionKind }),
|
|
883
893
|
workspace,
|
|
884
894
|
...(cli.worktreeRequested
|
package/dist/core/runtime.d.ts
CHANGED
|
@@ -322,8 +322,15 @@ export interface ToolExecutionContext {
|
|
|
322
322
|
permissionPhase?: 'request' | 'execute';
|
|
323
323
|
permissionApproved?: boolean;
|
|
324
324
|
}
|
|
325
|
+
export interface ToolSchedulingPolicy {
|
|
326
|
+
concurrency: 'concurrent' | 'exclusive';
|
|
327
|
+
cancelOnInterrupt?: boolean;
|
|
328
|
+
abortGroupOnError?: boolean;
|
|
329
|
+
startAfterAssistant?: boolean;
|
|
330
|
+
}
|
|
325
331
|
export interface ToolRegistry {
|
|
326
332
|
definitions(): readonly ModelToolDefinition[];
|
|
333
|
+
schedulingPolicy?(call: ModelToolCall): ToolSchedulingPolicy;
|
|
327
334
|
prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
|
|
328
335
|
execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
329
336
|
}
|
|
@@ -514,6 +521,9 @@ export declare class AgentRuntime {
|
|
|
514
521
|
recoverToolCalls(calls: readonly ModelToolCall[], request: AgentToolRecoveryRequest): Promise<ToolExecutionResult[]>;
|
|
515
522
|
executeDirectToolCall(call: ModelToolCall, request: AgentToolRecoveryRequest): Promise<ToolExecutionResult>;
|
|
516
523
|
private completeToolCall;
|
|
524
|
+
private executeScheduledToolCall;
|
|
525
|
+
private presentToolResult;
|
|
526
|
+
private persistToolCall;
|
|
517
527
|
private executeTool;
|
|
518
528
|
private requireRecoveryApproval;
|
|
519
529
|
private cancel;
|