praxis-agent 0.28.0 → 0.29.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-service.d.ts +1 -0
- package/dist/application/session-service.js +129 -13
- package/dist/application/subagent-service.js +7 -1
- package/dist/cli-runtime.js +51 -20
- package/dist/compatibility/claude/context.d.ts +19 -4
- package/dist/compatibility/claude/context.js +198 -24
- package/dist/core/context.d.ts +13 -0
- package/dist/core/prompt-composer.d.ts +27 -0
- package/dist/core/prompt-composer.js +99 -0
- package/dist/core/runtime.d.ts +4 -0
- package/dist/core/runtime.js +5 -0
- package/dist/mcp/claude-mcp-tools.d.ts +9 -0
- package/dist/mcp/claude-mcp-tools.js +23 -0
- package/dist/providers/anthropic-compatible.js +15 -0
- package/package.json +1 -1
|
@@ -208,6 +208,7 @@ export declare class ClaudeSessionService {
|
|
|
208
208
|
private applyPermissionUpdates;
|
|
209
209
|
close(): Promise<void>;
|
|
210
210
|
transitionHookSession(sessionId: string, reason: Exclude<HookSessionEndReason, 'other'>): Promise<void>;
|
|
211
|
+
reloadContextResources(sessionId: string): void;
|
|
211
212
|
createHostedToolRegistry(sessionId: string): ToolRegistry;
|
|
212
213
|
run(prompt: string, signal?: AbortSignal, sessionId?: string, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[]): Promise<SessionRunResult>;
|
|
213
214
|
runShell(command: string, signal?: AbortSignal, sessionId?: string, name?: string): Promise<SessionRunResult>;
|
|
@@ -756,6 +756,16 @@ export class ClaudeSessionService {
|
|
|
756
756
|
}
|
|
757
757
|
async transitionHookSession(sessionId, reason) {
|
|
758
758
|
await this.hookLifecycle.transition(sessionId, reason);
|
|
759
|
+
this.options.contextAssembler?.invalidate?.({
|
|
760
|
+
lifecycleId: sessionId,
|
|
761
|
+
reason: reason === 'resume' ? 'restore' : 'clear',
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
reloadContextResources(sessionId) {
|
|
765
|
+
this.options.contextAssembler?.invalidate?.({
|
|
766
|
+
lifecycleId: sessionId,
|
|
767
|
+
reason: 'resource-reload',
|
|
768
|
+
});
|
|
759
769
|
}
|
|
760
770
|
createHostedToolRegistry(sessionId) {
|
|
761
771
|
const baseTools = this.options.tools;
|
|
@@ -996,6 +1006,7 @@ export class ClaudeSessionService {
|
|
|
996
1006
|
}
|
|
997
1007
|
const assembledContext = await this.options.contextAssembler?.assemble({
|
|
998
1008
|
cwd: this.activeCwd(),
|
|
1009
|
+
lifecycleId: activeSessionId,
|
|
999
1010
|
});
|
|
1000
1011
|
const messages = [
|
|
1001
1012
|
...(assembledContext?.systemMessages ?? []),
|
|
@@ -1106,13 +1117,18 @@ export class ClaudeSessionService {
|
|
|
1106
1117
|
const agent = this.resolveAgent(agentName);
|
|
1107
1118
|
const provider = this.providerForAgent(agent);
|
|
1108
1119
|
this.activeProvider = provider;
|
|
1120
|
+
const agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
1109
1121
|
const assembledContext = await this.options.contextAssembler?.assemble({
|
|
1110
1122
|
cwd: this.activeCwd(),
|
|
1123
|
+
lifecycleId: sessionId,
|
|
1124
|
+
...(agentSystem ? { mode: 'agent', baseSystemPrompt: agentSystem } : {}),
|
|
1111
1125
|
});
|
|
1112
|
-
const
|
|
1113
|
-
const assembledSystemMessages =
|
|
1126
|
+
const hasPromptManifest = assembledContext?.promptSections !== undefined;
|
|
1127
|
+
const assembledSystemMessages = hasPromptManifest
|
|
1128
|
+
? (assembledContext?.systemMessages ?? [])
|
|
1129
|
+
: this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
|
|
1114
1130
|
const contextMessages = [
|
|
1115
|
-
...(agentSystem
|
|
1131
|
+
...(!hasPromptManifest && agentSystem
|
|
1116
1132
|
? [{ role: 'system', content: agentSystem }]
|
|
1117
1133
|
: []),
|
|
1118
1134
|
...assembledSystemMessages,
|
|
@@ -1410,12 +1426,22 @@ export class ClaudeSessionService {
|
|
|
1410
1426
|
this.sessionCwds.set(sessionId, cwd);
|
|
1411
1427
|
this.runtimeCwd = cwd;
|
|
1412
1428
|
this.options.workspace?.setCwd(cwd);
|
|
1429
|
+
this.options.contextAssembler?.invalidate?.({
|
|
1430
|
+
lifecycleId: sessionId,
|
|
1431
|
+
reason: 'cwd',
|
|
1432
|
+
});
|
|
1413
1433
|
return cwd;
|
|
1414
1434
|
}
|
|
1415
1435
|
this.runtimeCwd = cwd;
|
|
1416
1436
|
this.options.workspace?.setCwd(cwd);
|
|
1417
1437
|
if (sessionId)
|
|
1418
1438
|
this.sessionCwds.set(sessionId, cwd);
|
|
1439
|
+
if (sessionId) {
|
|
1440
|
+
this.options.contextAssembler?.invalidate?.({
|
|
1441
|
+
lifecycleId: sessionId,
|
|
1442
|
+
reason: 'cwd',
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1419
1445
|
return cwd;
|
|
1420
1446
|
}
|
|
1421
1447
|
async recordCdUsage(sessionId) {
|
|
@@ -1791,6 +1817,10 @@ export class ClaudeSessionService {
|
|
|
1791
1817
|
if (result.status === 'conflict') {
|
|
1792
1818
|
throw new Error('Generated Claude fork session already exists');
|
|
1793
1819
|
}
|
|
1820
|
+
this.options.contextAssembler?.invalidate?.({
|
|
1821
|
+
lifecycleId: sessionId,
|
|
1822
|
+
reason: 'fork',
|
|
1823
|
+
});
|
|
1794
1824
|
return { sessionId, parentSessionId };
|
|
1795
1825
|
}
|
|
1796
1826
|
async ensureFork(parentSessionId, sessionId, checkpoint) {
|
|
@@ -1799,8 +1829,13 @@ export class ClaudeSessionService {
|
|
|
1799
1829
|
const expected = await this.nativeForkEntries(parentSessionId, sessionId, checkpoint?.resumeSessionAt, checkpoint?.entryCount);
|
|
1800
1830
|
const target = this.store(sessionId);
|
|
1801
1831
|
const created = await target.create(expected);
|
|
1802
|
-
if (created.status === 'created')
|
|
1832
|
+
if (created.status === 'created') {
|
|
1833
|
+
this.options.contextAssembler?.invalidate?.({
|
|
1834
|
+
lifecycleId: sessionId,
|
|
1835
|
+
reason: 'fork',
|
|
1836
|
+
});
|
|
1803
1837
|
return { sessionId, parentSessionId };
|
|
1838
|
+
}
|
|
1804
1839
|
const existing = await target.withLease((lease) => lease.load());
|
|
1805
1840
|
if (existing.status === 'conflict') {
|
|
1806
1841
|
throw new Error(`Claude transcript fork conflict: ${existing.reason}`);
|
|
@@ -1810,6 +1845,10 @@ export class ClaudeSessionService {
|
|
|
1810
1845
|
JSON.stringify(entry))) {
|
|
1811
1846
|
throw new Error('Claude handoff target is not the expected native fork');
|
|
1812
1847
|
}
|
|
1848
|
+
this.options.contextAssembler?.invalidate?.({
|
|
1849
|
+
lifecycleId: sessionId,
|
|
1850
|
+
reason: 'fork',
|
|
1851
|
+
});
|
|
1813
1852
|
return { sessionId, parentSessionId };
|
|
1814
1853
|
}
|
|
1815
1854
|
async nativeForkEntries(parentSessionId, sessionId, resumeSessionAt, sourceEntryCount) {
|
|
@@ -2442,6 +2481,10 @@ export class ClaudeSessionService {
|
|
|
2442
2481
|
currentTurnToolCalls += 1;
|
|
2443
2482
|
const transition = this.worktreeManager?.consumeTransition(call.id);
|
|
2444
2483
|
if (transition) {
|
|
2484
|
+
this.options.contextAssembler?.invalidate?.({
|
|
2485
|
+
lifecycleId: sessionId,
|
|
2486
|
+
reason: 'worktree',
|
|
2487
|
+
});
|
|
2445
2488
|
const stateEntry = {
|
|
2446
2489
|
type: 'worktree-state',
|
|
2447
2490
|
worktreeSession: transition.state,
|
|
@@ -2584,29 +2627,83 @@ export class ClaudeSessionService {
|
|
|
2584
2627
|
let planModeMessage;
|
|
2585
2628
|
let sessionMemoryMessage = null;
|
|
2586
2629
|
let contextMessages = [];
|
|
2630
|
+
let stableSystemMessageCount;
|
|
2587
2631
|
const refreshRuntimeContext = async () => {
|
|
2588
|
-
assembledContext = await this.options.contextAssembler?.assemble({
|
|
2589
|
-
cwd: this.activeCwd(),
|
|
2590
|
-
});
|
|
2591
2632
|
agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
2592
|
-
const assembledSystemMessages = this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
|
|
2593
2633
|
planModeMessage =
|
|
2594
2634
|
this.options.interactiveTools?.contextMessage(sessionId);
|
|
2595
2635
|
sessionMemoryMessage = sessionMemory
|
|
2596
2636
|
? this.sessionMemoryMessage(await sessionMemory.summary())
|
|
2597
2637
|
: null;
|
|
2598
|
-
|
|
2638
|
+
const additionalSections = [
|
|
2639
|
+
...(planModeMessage
|
|
2640
|
+
? [
|
|
2641
|
+
{
|
|
2642
|
+
id: 'plan-mode',
|
|
2643
|
+
placement: 'system',
|
|
2644
|
+
stability: 'volatile',
|
|
2645
|
+
content: planModeMessage,
|
|
2646
|
+
},
|
|
2647
|
+
]
|
|
2648
|
+
: []),
|
|
2649
|
+
...(sessionMemoryMessage
|
|
2650
|
+
? [
|
|
2651
|
+
{
|
|
2652
|
+
id: 'session-memory',
|
|
2653
|
+
placement: 'system',
|
|
2654
|
+
stability: 'volatile',
|
|
2655
|
+
content: sessionMemoryMessage,
|
|
2656
|
+
},
|
|
2657
|
+
]
|
|
2658
|
+
: []),
|
|
2659
|
+
...(this.options.brief
|
|
2660
|
+
? [
|
|
2661
|
+
{
|
|
2662
|
+
id: 'brief-output',
|
|
2663
|
+
placement: 'system',
|
|
2664
|
+
stability: 'volatile',
|
|
2665
|
+
content: CLAUDE_USER_MESSAGE_PROMPT,
|
|
2666
|
+
},
|
|
2667
|
+
]
|
|
2668
|
+
: []),
|
|
2669
|
+
...(this.options.structuredOutputSchema
|
|
2670
|
+
? [
|
|
2671
|
+
{
|
|
2672
|
+
id: 'structured-output',
|
|
2673
|
+
placement: 'system',
|
|
2674
|
+
stability: 'volatile',
|
|
2675
|
+
content: 'You MUST call StructuredOutput exactly once at the end with a value matching the requested JSON Schema.',
|
|
2676
|
+
},
|
|
2677
|
+
]
|
|
2678
|
+
: []),
|
|
2679
|
+
];
|
|
2680
|
+
assembledContext = await this.options.contextAssembler?.assemble({
|
|
2681
|
+
cwd: this.activeCwd(),
|
|
2682
|
+
lifecycleId: sessionId,
|
|
2599
2683
|
...(agentSystem
|
|
2684
|
+
? { mode: 'agent', baseSystemPrompt: agentSystem }
|
|
2685
|
+
: {}),
|
|
2686
|
+
additionalSections,
|
|
2687
|
+
});
|
|
2688
|
+
const hasPromptManifest = assembledContext?.promptSections !== undefined;
|
|
2689
|
+
stableSystemMessageCount = hasPromptManifest
|
|
2690
|
+
? assembledContext?.stableSystemSectionCount
|
|
2691
|
+
: undefined;
|
|
2692
|
+
const assembledSystemMessages = hasPromptManifest
|
|
2693
|
+
? (assembledContext?.systemMessages ?? [])
|
|
2694
|
+
: this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
|
|
2695
|
+
contextMessages = [
|
|
2696
|
+
...(!hasPromptManifest && agentSystem
|
|
2600
2697
|
? [{ role: 'system', content: agentSystem }]
|
|
2601
2698
|
: []),
|
|
2602
2699
|
...assembledSystemMessages,
|
|
2603
|
-
...(planModeMessage
|
|
2700
|
+
...(!hasPromptManifest && planModeMessage
|
|
2604
2701
|
? [{ role: 'system', content: planModeMessage }]
|
|
2605
2702
|
: []),
|
|
2606
|
-
...(sessionMemoryMessage
|
|
2703
|
+
...(!hasPromptManifest && sessionMemoryMessage
|
|
2607
2704
|
? [{ role: 'system', content: sessionMemoryMessage }]
|
|
2608
2705
|
: []),
|
|
2609
|
-
...(this.options.brief
|
|
2706
|
+
...(!hasPromptManifest && this.options.brief
|
|
2610
2707
|
? [
|
|
2611
2708
|
{
|
|
2612
2709
|
role: 'system',
|
|
@@ -2614,7 +2711,7 @@ export class ClaudeSessionService {
|
|
|
2614
2711
|
},
|
|
2615
2712
|
]
|
|
2616
2713
|
: []),
|
|
2617
|
-
...(this.options.structuredOutputSchema
|
|
2714
|
+
...(!hasPromptManifest && this.options.structuredOutputSchema
|
|
2618
2715
|
? [
|
|
2619
2716
|
{
|
|
2620
2717
|
role: 'system',
|
|
@@ -2915,6 +3012,10 @@ export class ClaudeSessionService {
|
|
|
2915
3012
|
if (outcome)
|
|
2916
3013
|
await recordHookOutcome(outcome);
|
|
2917
3014
|
}
|
|
3015
|
+
this.options.contextAssembler?.invalidate?.({
|
|
3016
|
+
lifecycleId: sessionId,
|
|
3017
|
+
reason: 'compact',
|
|
3018
|
+
});
|
|
2918
3019
|
await refreshRuntimeContext();
|
|
2919
3020
|
this.options.eventSink?.({
|
|
2920
3021
|
type: 'compact-boundary',
|
|
@@ -3086,6 +3187,9 @@ export class ClaudeSessionService {
|
|
|
3086
3187
|
...contextMessages,
|
|
3087
3188
|
...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
|
|
3088
3189
|
],
|
|
3190
|
+
...(stableSystemMessageCount === undefined
|
|
3191
|
+
? {}
|
|
3192
|
+
: { stableSystemMessageCount }),
|
|
3089
3193
|
cwd: this.activeCwd(),
|
|
3090
3194
|
toolResultDirectory,
|
|
3091
3195
|
observer,
|
|
@@ -3099,6 +3203,12 @@ export class ClaudeSessionService {
|
|
|
3099
3203
|
: {}),
|
|
3100
3204
|
reloadMessages: async () => {
|
|
3101
3205
|
await compactIfNeeded([], currentTurnUserMessages ?? []);
|
|
3206
|
+
if (stableSystemMessageCount === undefined) {
|
|
3207
|
+
delete runtimeRequest.stableSystemMessageCount;
|
|
3208
|
+
}
|
|
3209
|
+
else {
|
|
3210
|
+
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
3211
|
+
}
|
|
3102
3212
|
return [
|
|
3103
3213
|
...contextMessages,
|
|
3104
3214
|
...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
|
|
@@ -3213,6 +3323,12 @@ export class ClaudeSessionService {
|
|
|
3213
3323
|
...contextMessages,
|
|
3214
3324
|
...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
|
|
3215
3325
|
];
|
|
3326
|
+
if (stableSystemMessageCount === undefined) {
|
|
3327
|
+
delete runtimeRequest.stableSystemMessageCount;
|
|
3328
|
+
}
|
|
3329
|
+
else {
|
|
3330
|
+
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
3331
|
+
}
|
|
3216
3332
|
try {
|
|
3217
3333
|
result = await attemptMainTurn();
|
|
3218
3334
|
}
|
|
@@ -1493,10 +1493,16 @@ export class ClaudeSubagentExecutor {
|
|
|
1493
1493
|
const assembleMessages = async () => {
|
|
1494
1494
|
const assembledContext = await this.options.contextAssembler?.assemble({
|
|
1495
1495
|
cwd,
|
|
1496
|
+
lifecycleId: options.agentId,
|
|
1497
|
+
mode: 'subagent',
|
|
1498
|
+
baseSystemPrompt: system,
|
|
1496
1499
|
});
|
|
1500
|
+
const composedSubagentPolicy = assembledContext?.promptSections?.some((section) => section.id === 'subagent-policy');
|
|
1497
1501
|
const messages = [
|
|
1498
1502
|
...(assembledContext?.systemMessages ?? []),
|
|
1499
|
-
|
|
1503
|
+
...(composedSubagentPolicy
|
|
1504
|
+
? []
|
|
1505
|
+
: [{ role: 'system', content: system }]),
|
|
1500
1506
|
...injectFirstUserMessageContext(projectClaudeModelMessages(snapshot.entries), assembledContext?.firstUserMessageContext),
|
|
1501
1507
|
...preloadedSkills,
|
|
1502
1508
|
];
|
package/dist/cli-runtime.js
CHANGED
|
@@ -802,12 +802,6 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
802
802
|
? {}
|
|
803
803
|
: { permissionMode: interactivePermissionMode }),
|
|
804
804
|
}, cwd);
|
|
805
|
-
const effectiveAppendSystemPrompt = [
|
|
806
|
-
runtimeSettingsPrompt,
|
|
807
|
-
cli.appendSystemPrompt,
|
|
808
|
-
]
|
|
809
|
-
.filter((value) => value !== undefined)
|
|
810
|
-
.join('\n');
|
|
811
805
|
const debug = cli.debug !== undefined || cli.debugFile !== undefined
|
|
812
806
|
? createCliDebugSink(eventSink, {
|
|
813
807
|
cwd,
|
|
@@ -1175,6 +1169,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1175
1169
|
});
|
|
1176
1170
|
return filterDisabledMcpResources(candidates, await management.disabled());
|
|
1177
1171
|
};
|
|
1172
|
+
let contextAssembler;
|
|
1178
1173
|
const mcpTools = await ClaudeMcpToolRegistry.connect({
|
|
1179
1174
|
base: simpleMode
|
|
1180
1175
|
? localTools
|
|
@@ -1219,6 +1214,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1219
1214
|
configRoot,
|
|
1220
1215
|
onWarning: (message) => runtimeEventSink({ type: 'warning', message }),
|
|
1221
1216
|
onPromptsChanged: (prompts) => extensions.setMcpPrompts(prompts),
|
|
1217
|
+
onInstructionsChanged: () => contextAssembler?.invalidate({ reason: 'tool-pool' }),
|
|
1222
1218
|
authenticateServer: async (name) => {
|
|
1223
1219
|
const record = await new ClaudeMcpManagement({
|
|
1224
1220
|
dataPlane,
|
|
@@ -1374,6 +1370,54 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1374
1370
|
},
|
|
1375
1371
|
})
|
|
1376
1372
|
: undefined;
|
|
1373
|
+
contextAssembler = new ClaudeContextAssembler({
|
|
1374
|
+
loadResources: loadContextResources,
|
|
1375
|
+
loadDynamicContext: (runtimeCwd = workspace.cwd()) => loadClaudeDynamicContext({
|
|
1376
|
+
cwd: runtimeCwd,
|
|
1377
|
+
...(memoryDirectory ? { memoryDirectory } : {}),
|
|
1378
|
+
}),
|
|
1379
|
+
loadMcpInstructions: async () => mcpTools.instructions(),
|
|
1380
|
+
loadSessionGuidance: async () => {
|
|
1381
|
+
const toolNames = [
|
|
1382
|
+
...new Set([
|
|
1383
|
+
...filteredTools.definitions().map((definition) => definition.name),
|
|
1384
|
+
...selectedTaskRuntimeTools,
|
|
1385
|
+
...selectedScheduledTools,
|
|
1386
|
+
...selectedWorkflowTools,
|
|
1387
|
+
...selectedWorktreeTools,
|
|
1388
|
+
...selectedInteractiveTools,
|
|
1389
|
+
...(enableSubagents ? selectedAgentTools : []),
|
|
1390
|
+
]),
|
|
1391
|
+
].sort();
|
|
1392
|
+
const skillNames = extensions
|
|
1393
|
+
.modelInvocableSkills()
|
|
1394
|
+
.map((skill) => skill.name)
|
|
1395
|
+
.sort();
|
|
1396
|
+
if (toolNames.length === 0 &&
|
|
1397
|
+
skillNames.length === 0 &&
|
|
1398
|
+
runtimeSettingsPrompt === undefined)
|
|
1399
|
+
return undefined;
|
|
1400
|
+
return [
|
|
1401
|
+
'# Session capabilities',
|
|
1402
|
+
...(toolNames.length > 0
|
|
1403
|
+
? [`Enabled tools: ${toolNames.join(', ')}`]
|
|
1404
|
+
: []),
|
|
1405
|
+
...(skillNames.length > 0
|
|
1406
|
+
? [`Model-invocable skills: ${skillNames.join(', ')}`]
|
|
1407
|
+
: []),
|
|
1408
|
+
'Use a capability only when it directly helps complete the request, and follow its declared input contract.',
|
|
1409
|
+
...(runtimeSettingsPrompt ? ['', runtimeSettingsPrompt] : []),
|
|
1410
|
+
].join('\n');
|
|
1411
|
+
},
|
|
1412
|
+
excludeDynamicSystemPromptSections: cli.excludeDynamicSystemPromptSections,
|
|
1413
|
+
...(cli.systemPrompt === undefined
|
|
1414
|
+
? {}
|
|
1415
|
+
: { systemPrompt: cli.systemPrompt }),
|
|
1416
|
+
...(cli.appendSystemPrompt
|
|
1417
|
+
? { appendSystemPrompt: cli.appendSystemPrompt }
|
|
1418
|
+
: {}),
|
|
1419
|
+
...(simpleMode ? { bare: true } : {}),
|
|
1420
|
+
});
|
|
1377
1421
|
const service = new ClaudeSessionService({
|
|
1378
1422
|
...options,
|
|
1379
1423
|
provider: hostedToolProvider,
|
|
@@ -1409,20 +1453,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1409
1453
|
...(interactiveTools ? { interactiveTools } : {}),
|
|
1410
1454
|
...(hooks ? { hooks } : {}),
|
|
1411
1455
|
...(selectedMainAgent ? { agent: selectedMainAgent } : {}),
|
|
1412
|
-
contextAssembler
|
|
1413
|
-
loadResources: loadContextResources,
|
|
1414
|
-
loadDynamicContext: (runtimeCwd = workspace.cwd()) => loadClaudeDynamicContext({
|
|
1415
|
-
cwd: runtimeCwd,
|
|
1416
|
-
...(memoryDirectory ? { memoryDirectory } : {}),
|
|
1417
|
-
}),
|
|
1418
|
-
excludeDynamicSystemPromptSections: cli.excludeDynamicSystemPromptSections,
|
|
1419
|
-
...(cli.systemPrompt === undefined
|
|
1420
|
-
? {}
|
|
1421
|
-
: { systemPrompt: cli.systemPrompt }),
|
|
1422
|
-
...(effectiveAppendSystemPrompt
|
|
1423
|
-
? { appendSystemPrompt: effectiveAppendSystemPrompt }
|
|
1424
|
-
: {}),
|
|
1425
|
-
}),
|
|
1456
|
+
contextAssembler,
|
|
1426
1457
|
conditionalRuleResolver: new ClaudeConditionalRuleResolver({
|
|
1427
1458
|
loadResources: loadContextResources,
|
|
1428
1459
|
}),
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import type { AssembledContext, ContextAssembler } from '../../core/context.js';
|
|
1
|
+
import type { AssembledContext, ContextAssembler, ContextAssemblyOptions, ContextInvalidationOptions } from '../../core/context.js';
|
|
2
2
|
import { type ClaudeDynamicContextSections } from './dynamic-context.js';
|
|
3
3
|
import type { ClaudeContextResources, ClaudeConditionalRule } from './shared-resources.js';
|
|
4
4
|
export interface ClaudeContextAssemblerOptions {
|
|
5
5
|
loadResources(cwd?: string): Promise<ClaudeContextResources>;
|
|
6
6
|
loadDynamicContext?(cwd?: string): Promise<ClaudeDynamicContextSections>;
|
|
7
|
+
loadMcpInstructions?(): Promise<readonly ClaudeMcpInstruction[]>;
|
|
8
|
+
loadSessionGuidance?(): Promise<string | undefined>;
|
|
7
9
|
excludeDynamicSystemPromptSections?: boolean;
|
|
8
10
|
systemPrompt?: string;
|
|
9
11
|
appendSystemPrompt?: string;
|
|
12
|
+
bare?: boolean;
|
|
13
|
+
now?(): Date;
|
|
14
|
+
}
|
|
15
|
+
export interface ClaudeMcpInstruction {
|
|
16
|
+
server: string;
|
|
17
|
+
instructions: string;
|
|
10
18
|
}
|
|
11
19
|
export type ClaudeConditionalRuleResolverOptions = ClaudeContextAssemblerOptions;
|
|
12
20
|
export declare class ClaudeConditionalRuleResolver {
|
|
@@ -16,9 +24,16 @@ export declare class ClaudeConditionalRuleResolver {
|
|
|
16
24
|
}
|
|
17
25
|
export declare class ClaudeContextAssembler implements ContextAssembler {
|
|
18
26
|
private readonly options;
|
|
27
|
+
private readonly composer;
|
|
28
|
+
private readonly snapshots;
|
|
19
29
|
constructor(options: ClaudeContextAssemblerOptions);
|
|
20
|
-
assemble(options?:
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
assemble(options?: ContextAssemblyOptions): Promise<AssembledContext>;
|
|
31
|
+
invalidate(options: ContextInvalidationOptions): void;
|
|
32
|
+
private snapshot;
|
|
33
|
+
private loadResources;
|
|
34
|
+
private loadDynamicContext;
|
|
35
|
+
private loadMcpInstructions;
|
|
36
|
+
private loadSessionGuidance;
|
|
37
|
+
private currentDate;
|
|
23
38
|
}
|
|
24
39
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, matchesGlob, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { PromptComposer, } from '../../core/prompt-composer.js';
|
|
2
3
|
import { renderClaudeDynamicSystemContext, renderClaudeDynamicUserContext, } from './dynamic-context.js';
|
|
3
4
|
function renderResources(title, resources) {
|
|
4
5
|
const rendered = resources
|
|
@@ -40,22 +41,32 @@ export class ClaudeConditionalRuleResolver {
|
|
|
40
41
|
}
|
|
41
42
|
export class ClaudeContextAssembler {
|
|
42
43
|
options;
|
|
44
|
+
composer = new PromptComposer();
|
|
45
|
+
snapshots = new Map();
|
|
43
46
|
constructor(options) {
|
|
44
47
|
this.options = options;
|
|
45
48
|
}
|
|
46
49
|
async assemble(options = {}) {
|
|
47
|
-
const
|
|
50
|
+
const snapshot = this.snapshot(options);
|
|
51
|
+
const mode = options.mode ??
|
|
52
|
+
(this.options.systemPrompt !== undefined
|
|
53
|
+
? 'custom'
|
|
54
|
+
: this.options.bare
|
|
55
|
+
? 'bare'
|
|
56
|
+
: 'default');
|
|
57
|
+
const resources = mode === 'bare'
|
|
58
|
+
? { instructions: [], conditionalRules: [], memoryIndex: null }
|
|
59
|
+
: await this.loadResources(snapshot, options.cwd);
|
|
48
60
|
const sections = [
|
|
49
61
|
renderResources('Instructions', resources.instructions),
|
|
50
62
|
renderResources('Auto-memory', resources.memoryIndex ? [limitMemoryIndex(resources.memoryIndex)] : []),
|
|
51
63
|
].filter((section) => section !== null);
|
|
52
|
-
const
|
|
53
|
-
if (this.options.systemPrompt !== undefined) {
|
|
54
|
-
messages.push({ role: 'system', content: this.options.systemPrompt });
|
|
55
|
-
}
|
|
64
|
+
const sessionSections = [];
|
|
56
65
|
if (sections.length > 0) {
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
sessionSections.push({
|
|
67
|
+
id: 'shared-resources',
|
|
68
|
+
placement: 'system',
|
|
69
|
+
stability: 'session',
|
|
59
70
|
content: `# Shared Claude context
|
|
60
71
|
|
|
61
72
|
Instructions are ordered from broadest to most specific. Auto-memory is background context and does not override instructions.
|
|
@@ -63,36 +74,199 @@ Instructions are ordered from broadest to most specific. Auto-memory is backgrou
|
|
|
63
74
|
${sections.join('\n\n')}`,
|
|
64
75
|
});
|
|
65
76
|
}
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
const tailSections = [];
|
|
78
|
+
if (mode !== 'bare') {
|
|
79
|
+
sessionSections.push({
|
|
80
|
+
id: 'current-date',
|
|
81
|
+
placement: 'system',
|
|
82
|
+
stability: 'session',
|
|
83
|
+
content: `# Current date\n${this.currentDate(snapshot)}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (mode !== 'bare') {
|
|
87
|
+
const guidance = await this.loadSessionGuidance(snapshot);
|
|
88
|
+
if (guidance?.trim()) {
|
|
89
|
+
sessionSections.push({
|
|
90
|
+
id: 'session-guidance',
|
|
91
|
+
placement: 'system',
|
|
92
|
+
stability: 'session',
|
|
93
|
+
content: guidance,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const mcpInstructions = await this.loadMcpInstructions(snapshot);
|
|
97
|
+
tailSections.push(...mcpInstructions
|
|
98
|
+
.filter(({ instructions }) => instructions.trim().length > 0)
|
|
99
|
+
.sort((left, right) => left.server.localeCompare(right.server))
|
|
100
|
+
.map(({ server, instructions }) => ({
|
|
101
|
+
id: `mcp-instructions:${server}`,
|
|
102
|
+
placement: 'system',
|
|
103
|
+
stability: 'volatile',
|
|
104
|
+
content: `# MCP server instructions: ${server}\n${instructions.trimEnd()}`,
|
|
105
|
+
})));
|
|
106
|
+
}
|
|
107
|
+
if (mode !== 'bare' && this.options.loadDynamicContext !== undefined) {
|
|
108
|
+
const dynamic = await this.loadDynamicContext(snapshot, options.cwd);
|
|
70
109
|
if (this.options.excludeDynamicSystemPromptSections) {
|
|
71
110
|
if (dynamic.memory) {
|
|
72
|
-
|
|
111
|
+
sessionSections.push({
|
|
112
|
+
id: 'memory-mechanics',
|
|
113
|
+
placement: 'system',
|
|
114
|
+
stability: 'session',
|
|
115
|
+
content: dynamic.memory,
|
|
116
|
+
});
|
|
73
117
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
118
|
+
tailSections.push({
|
|
119
|
+
id: 'relocated-runtime-context',
|
|
120
|
+
placement: 'first-user',
|
|
121
|
+
stability: 'session',
|
|
122
|
+
content: renderClaudeDynamicUserContext({
|
|
123
|
+
environment: dynamic.environment,
|
|
124
|
+
...(dynamic.gitStatus ? { gitStatus: dynamic.gitStatus } : {}),
|
|
125
|
+
}),
|
|
77
126
|
});
|
|
78
127
|
}
|
|
79
128
|
else {
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
sessionSections.push({
|
|
130
|
+
id: 'runtime-context',
|
|
131
|
+
placement: 'system',
|
|
132
|
+
stability: 'session',
|
|
82
133
|
content: renderClaudeDynamicSystemContext(dynamic),
|
|
83
134
|
});
|
|
84
135
|
}
|
|
85
136
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
for (const section of options.additionalSections ?? []) {
|
|
138
|
+
const target = section.stability === 'volatile' || section.placement === 'first-user'
|
|
139
|
+
? tailSections
|
|
140
|
+
: sessionSections;
|
|
141
|
+
target.push(section);
|
|
91
142
|
}
|
|
143
|
+
const composition = this.composer.compose({
|
|
144
|
+
mode,
|
|
145
|
+
...(options.baseSystemPrompt !== undefined
|
|
146
|
+
? { baseSystemPrompt: options.baseSystemPrompt }
|
|
147
|
+
: this.options.systemPrompt !== undefined
|
|
148
|
+
? { baseSystemPrompt: this.options.systemPrompt }
|
|
149
|
+
: {}),
|
|
150
|
+
...(this.options.appendSystemPrompt !== undefined
|
|
151
|
+
? { appendSystemPrompt: this.options.appendSystemPrompt }
|
|
152
|
+
: {}),
|
|
153
|
+
sessionSections,
|
|
154
|
+
tailSections,
|
|
155
|
+
});
|
|
92
156
|
return {
|
|
93
|
-
systemMessages:
|
|
94
|
-
|
|
157
|
+
systemMessages: composition.systemMessages,
|
|
158
|
+
promptSections: composition.sections,
|
|
159
|
+
stableSystemSectionCount: composition.stableSystemSectionCount,
|
|
160
|
+
...(composition.firstUserMessageContext
|
|
161
|
+
? { firstUserMessageContext: composition.firstUserMessageContext }
|
|
162
|
+
: {}),
|
|
95
163
|
};
|
|
96
164
|
}
|
|
165
|
+
invalidate(options) {
|
|
166
|
+
for (const [key, snapshot] of this.snapshots) {
|
|
167
|
+
if (options.lifecycleId !== undefined &&
|
|
168
|
+
snapshot.lifecycleId !== options.lifecycleId)
|
|
169
|
+
continue;
|
|
170
|
+
if (options.reason === 'resource-reload' ||
|
|
171
|
+
options.reason === 'compact') {
|
|
172
|
+
delete snapshot.resources;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (options.reason === 'tool-pool') {
|
|
176
|
+
delete snapshot.mcpInstructions;
|
|
177
|
+
delete snapshot.sessionGuidance;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
this.snapshots.delete(key);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
snapshot(options) {
|
|
184
|
+
if (!options.lifecycleId)
|
|
185
|
+
return undefined;
|
|
186
|
+
const key = JSON.stringify([options.lifecycleId, options.cwd ?? null]);
|
|
187
|
+
let snapshot = this.snapshots.get(key);
|
|
188
|
+
if (!snapshot) {
|
|
189
|
+
snapshot = {
|
|
190
|
+
lifecycleId: options.lifecycleId,
|
|
191
|
+
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
192
|
+
};
|
|
193
|
+
this.snapshots.set(key, snapshot);
|
|
194
|
+
}
|
|
195
|
+
return snapshot;
|
|
196
|
+
}
|
|
197
|
+
loadResources(snapshot, cwd) {
|
|
198
|
+
if (!snapshot)
|
|
199
|
+
return this.options.loadResources(cwd);
|
|
200
|
+
if (!snapshot.resources) {
|
|
201
|
+
const pending = this.options.loadResources(cwd);
|
|
202
|
+
snapshot.resources = pending;
|
|
203
|
+
void pending.catch(() => {
|
|
204
|
+
if (snapshot.resources === pending)
|
|
205
|
+
delete snapshot.resources;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return snapshot.resources;
|
|
209
|
+
}
|
|
210
|
+
loadDynamicContext(snapshot, cwd) {
|
|
211
|
+
const load = this.options.loadDynamicContext;
|
|
212
|
+
if (!load)
|
|
213
|
+
throw new Error('Dynamic context loader is unavailable');
|
|
214
|
+
if (!snapshot)
|
|
215
|
+
return load(cwd);
|
|
216
|
+
if (!snapshot.dynamic) {
|
|
217
|
+
const pending = load(cwd);
|
|
218
|
+
snapshot.dynamic = pending;
|
|
219
|
+
void pending.catch(() => {
|
|
220
|
+
if (snapshot.dynamic === pending)
|
|
221
|
+
delete snapshot.dynamic;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return snapshot.dynamic;
|
|
225
|
+
}
|
|
226
|
+
loadMcpInstructions(snapshot) {
|
|
227
|
+
const load = this.options.loadMcpInstructions;
|
|
228
|
+
if (!load)
|
|
229
|
+
return Promise.resolve([]);
|
|
230
|
+
if (!snapshot)
|
|
231
|
+
return load();
|
|
232
|
+
if (!snapshot.mcpInstructions) {
|
|
233
|
+
const pending = load();
|
|
234
|
+
snapshot.mcpInstructions = pending;
|
|
235
|
+
void pending.catch(() => {
|
|
236
|
+
if (snapshot.mcpInstructions === pending)
|
|
237
|
+
delete snapshot.mcpInstructions;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return snapshot.mcpInstructions;
|
|
241
|
+
}
|
|
242
|
+
loadSessionGuidance(snapshot) {
|
|
243
|
+
const load = this.options.loadSessionGuidance;
|
|
244
|
+
if (!load)
|
|
245
|
+
return Promise.resolve(undefined);
|
|
246
|
+
if (!snapshot)
|
|
247
|
+
return load();
|
|
248
|
+
if (!snapshot.sessionGuidance) {
|
|
249
|
+
const pending = load();
|
|
250
|
+
snapshot.sessionGuidance = pending;
|
|
251
|
+
void pending.catch(() => {
|
|
252
|
+
if (snapshot.sessionGuidance === pending)
|
|
253
|
+
delete snapshot.sessionGuidance;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return snapshot.sessionGuidance;
|
|
257
|
+
}
|
|
258
|
+
currentDate(snapshot) {
|
|
259
|
+
if (snapshot?.currentDate)
|
|
260
|
+
return snapshot.currentDate;
|
|
261
|
+
const now = (this.options.now ?? (() => new Date()))();
|
|
262
|
+
const date = [
|
|
263
|
+
String(now.getFullYear()).padStart(4, '0'),
|
|
264
|
+
String(now.getMonth() + 1).padStart(2, '0'),
|
|
265
|
+
String(now.getDate()).padStart(2, '0'),
|
|
266
|
+
].join('-');
|
|
267
|
+
if (snapshot)
|
|
268
|
+
snapshot.currentDate = date;
|
|
269
|
+
return date;
|
|
270
|
+
}
|
|
97
271
|
}
|
|
98
272
|
//# sourceMappingURL=context.js.map
|
package/dist/core/context.d.ts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import type { ModelMessage } from './runtime.js';
|
|
2
|
+
import type { PromptCompositionMode, PromptSection } from './prompt-composer.js';
|
|
2
3
|
export type SystemContextMessage = Extract<ModelMessage, {
|
|
3
4
|
role: 'system';
|
|
4
5
|
}>;
|
|
5
6
|
export interface AssembledContext {
|
|
6
7
|
systemMessages: readonly SystemContextMessage[];
|
|
7
8
|
firstUserMessageContext?: string;
|
|
9
|
+
promptSections?: readonly PromptSection[];
|
|
10
|
+
stableSystemSectionCount?: number;
|
|
8
11
|
}
|
|
9
12
|
export interface ContextAssemblyOptions {
|
|
10
13
|
cwd?: string;
|
|
14
|
+
lifecycleId?: string;
|
|
15
|
+
mode?: PromptCompositionMode;
|
|
16
|
+
baseSystemPrompt?: string;
|
|
17
|
+
additionalSections?: readonly PromptSection[];
|
|
18
|
+
}
|
|
19
|
+
export type ContextInvalidationReason = 'clear' | 'compact' | 'fork' | 'resource-reload' | 'restore' | 'tool-pool' | 'cwd' | 'worktree';
|
|
20
|
+
export interface ContextInvalidationOptions {
|
|
21
|
+
lifecycleId?: string;
|
|
22
|
+
reason: ContextInvalidationReason;
|
|
11
23
|
}
|
|
12
24
|
export interface ContextAssembler {
|
|
13
25
|
assemble(options?: ContextAssemblyOptions): Promise<AssembledContext>;
|
|
26
|
+
invalidate?(options: ContextInvalidationOptions): void;
|
|
14
27
|
}
|
|
15
28
|
export declare function injectFirstUserMessageContext(messages: readonly ModelMessage[], context: string | undefined): ModelMessage[];
|
|
16
29
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SystemContextMessage } from './context.js';
|
|
2
|
+
export type PromptSectionStability = 'static' | 'session' | 'volatile';
|
|
3
|
+
export type PromptSectionPlacement = 'system' | 'first-user';
|
|
4
|
+
export type PromptCompositionMode = 'default' | 'custom' | 'bare' | 'agent' | 'subagent';
|
|
5
|
+
export interface PromptSection {
|
|
6
|
+
id: string;
|
|
7
|
+
content: string;
|
|
8
|
+
stability: PromptSectionStability;
|
|
9
|
+
placement: PromptSectionPlacement;
|
|
10
|
+
}
|
|
11
|
+
export interface PromptComposition {
|
|
12
|
+
sections: readonly PromptSection[];
|
|
13
|
+
systemMessages: readonly SystemContextMessage[];
|
|
14
|
+
firstUserMessageContext?: string;
|
|
15
|
+
stableSystemSectionCount: number;
|
|
16
|
+
}
|
|
17
|
+
export interface PromptCompositionOptions {
|
|
18
|
+
mode: PromptCompositionMode;
|
|
19
|
+
baseSystemPrompt?: string;
|
|
20
|
+
appendSystemPrompt?: string;
|
|
21
|
+
sessionSections?: readonly PromptSection[];
|
|
22
|
+
tailSections?: readonly PromptSection[];
|
|
23
|
+
}
|
|
24
|
+
export declare class PromptComposer {
|
|
25
|
+
compose(options: PromptCompositionOptions): PromptComposition;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=prompt-composer.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const PRODUCT_POLICY = `# Praxis
|
|
2
|
+
|
|
3
|
+
You are Praxis, a local CLI coding agent. Work until the user's request is genuinely handled or a concrete blocker requires their input.
|
|
4
|
+
|
|
5
|
+
- Inspect relevant context before acting and use available tools deliberately.
|
|
6
|
+
- Preserve user data, existing changes, and project-specific instructions.
|
|
7
|
+
- Prefer focused, verifiable changes and report important outcomes clearly.
|
|
8
|
+
- Use a scratch or work area when the runtime provides one; keep temporary artifacts out of the user's project.
|
|
9
|
+
- Summarize or clear bulky intermediate results once their useful facts have been retained.
|
|
10
|
+
- Treat the context window as bounded: preserve decisions and evidence before reducing detail.
|
|
11
|
+
- Keep responses concise and use the user's language unless they request otherwise.`;
|
|
12
|
+
function baseSection(mode, content) {
|
|
13
|
+
if (mode === 'bare')
|
|
14
|
+
return undefined;
|
|
15
|
+
if (mode === 'default') {
|
|
16
|
+
return {
|
|
17
|
+
id: 'product-policy',
|
|
18
|
+
content: PRODUCT_POLICY,
|
|
19
|
+
placement: 'system',
|
|
20
|
+
stability: 'static',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (content === undefined || content.trim().length === 0) {
|
|
24
|
+
throw new Error(`${mode} prompt mode requires a base system prompt`);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
id: mode === 'custom'
|
|
28
|
+
? 'custom-system'
|
|
29
|
+
: mode === 'agent'
|
|
30
|
+
? 'agent-policy'
|
|
31
|
+
: 'subagent-policy',
|
|
32
|
+
content,
|
|
33
|
+
placement: 'system',
|
|
34
|
+
stability: 'session',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function normalizedSections(sections, group) {
|
|
38
|
+
return sections
|
|
39
|
+
.filter((section) => section.content.trim().length > 0)
|
|
40
|
+
.map((section) => {
|
|
41
|
+
if (!section.id.trim())
|
|
42
|
+
throw new Error('Prompt section id is required');
|
|
43
|
+
if (group === 'sessionSections' && section.stability === 'volatile') {
|
|
44
|
+
throw new Error('sessionSections cannot contain volatile prompt sections');
|
|
45
|
+
}
|
|
46
|
+
if (group === 'tailSections' &&
|
|
47
|
+
section.placement === 'system' &&
|
|
48
|
+
section.stability !== 'volatile') {
|
|
49
|
+
throw new Error('tailSections system entries must be volatile prompt sections');
|
|
50
|
+
}
|
|
51
|
+
return { ...section };
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export class PromptComposer {
|
|
55
|
+
compose(options) {
|
|
56
|
+
const base = baseSection(options.mode, options.baseSystemPrompt);
|
|
57
|
+
const sessionSections = normalizedSections(options.sessionSections ?? [], 'sessionSections');
|
|
58
|
+
const append = options.appendSystemPrompt?.trim()
|
|
59
|
+
? {
|
|
60
|
+
id: 'append-system',
|
|
61
|
+
content: options.appendSystemPrompt,
|
|
62
|
+
placement: 'system',
|
|
63
|
+
stability: 'session',
|
|
64
|
+
}
|
|
65
|
+
: undefined;
|
|
66
|
+
const tailSections = normalizedSections(options.tailSections ?? [], 'tailSections');
|
|
67
|
+
const sections = [
|
|
68
|
+
...(base ? [base] : []),
|
|
69
|
+
...sessionSections,
|
|
70
|
+
...(append ? [append] : []),
|
|
71
|
+
...tailSections,
|
|
72
|
+
];
|
|
73
|
+
const identities = new Set();
|
|
74
|
+
for (const section of sections) {
|
|
75
|
+
if (identities.has(section.id)) {
|
|
76
|
+
throw new Error(`Duplicate prompt section ${section.id}`);
|
|
77
|
+
}
|
|
78
|
+
identities.add(section.id);
|
|
79
|
+
}
|
|
80
|
+
const systemSections = sections.filter((section) => section.placement === 'system');
|
|
81
|
+
const firstVolatileSystemIndex = systemSections.findIndex((section) => section.stability === 'volatile');
|
|
82
|
+
const firstUserMessageContext = sections
|
|
83
|
+
.filter((section) => section.placement === 'first-user')
|
|
84
|
+
.map((section) => section.content)
|
|
85
|
+
.join('\n\n');
|
|
86
|
+
return {
|
|
87
|
+
sections,
|
|
88
|
+
systemMessages: systemSections.map((section) => ({
|
|
89
|
+
role: 'system',
|
|
90
|
+
content: section.content,
|
|
91
|
+
})),
|
|
92
|
+
stableSystemSectionCount: firstVolatileSystemIndex < 0
|
|
93
|
+
? systemSections.length
|
|
94
|
+
: firstVolatileSystemIndex,
|
|
95
|
+
...(firstUserMessageContext ? { firstUserMessageContext } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=prompt-composer.js.map
|
package/dist/core/runtime.d.ts
CHANGED
|
@@ -119,6 +119,8 @@ export type ModelStreamEvent = {
|
|
|
119
119
|
};
|
|
120
120
|
export interface ModelRequest {
|
|
121
121
|
messages: readonly ModelMessage[];
|
|
122
|
+
/** Number of leading system messages that form the stable prompt prefix. */
|
|
123
|
+
stableSystemMessageCount?: number;
|
|
122
124
|
tools?: readonly ModelToolDefinition[];
|
|
123
125
|
webSearch?: ModelWebSearch;
|
|
124
126
|
signal?: AbortSignal;
|
|
@@ -450,6 +452,8 @@ export interface AgentRuntimeOptions {
|
|
|
450
452
|
}
|
|
451
453
|
export interface AgentRunRequest {
|
|
452
454
|
messages: readonly ModelMessage[];
|
|
455
|
+
/** Number of leading system messages that form the stable prompt prefix. */
|
|
456
|
+
stableSystemMessageCount?: number;
|
|
453
457
|
cwd?: string;
|
|
454
458
|
toolResultDirectory?: string;
|
|
455
459
|
observer?: AgentRunObserver;
|
package/dist/core/runtime.js
CHANGED
|
@@ -342,6 +342,11 @@ export class AgentRuntime {
|
|
|
342
342
|
this.emit({ type: 'state', state: 'awaiting-model' });
|
|
343
343
|
const providerRequest = {
|
|
344
344
|
messages: prepareProviderMessages(messages, this.provider.capabilities.images === true, this.provider.capabilities.documents === true),
|
|
345
|
+
...(request.stableSystemMessageCount === undefined
|
|
346
|
+
? {}
|
|
347
|
+
: {
|
|
348
|
+
stableSystemMessageCount: request.stableSystemMessageCount,
|
|
349
|
+
}),
|
|
345
350
|
};
|
|
346
351
|
if (definitions.length > 0)
|
|
347
352
|
providerRequest.tools = definitions;
|
|
@@ -35,6 +35,7 @@ export interface ClaudeMcpRuntime {
|
|
|
35
35
|
authenticate(name: string): Promise<void>;
|
|
36
36
|
reload(): Promise<void>;
|
|
37
37
|
tools(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
|
|
38
|
+
instructions?(): readonly ClaudeMcpServerInstruction[];
|
|
38
39
|
connectAgent?(options: {
|
|
39
40
|
specs: readonly unknown[];
|
|
40
41
|
base: ToolRegistry;
|
|
@@ -47,6 +48,10 @@ export interface ClaudeMcpRuntime {
|
|
|
47
48
|
/** Release MCP transports and child processes owned by this runtime. */
|
|
48
49
|
close?(): Promise<void>;
|
|
49
50
|
}
|
|
51
|
+
export interface ClaudeMcpServerInstruction {
|
|
52
|
+
server: string;
|
|
53
|
+
instructions: string;
|
|
54
|
+
}
|
|
50
55
|
export interface ClaudeMcpConfigurationStatus {
|
|
51
56
|
name: string;
|
|
52
57
|
path: string;
|
|
@@ -67,6 +72,7 @@ export interface ClaudeMcpToolRegistryOptions {
|
|
|
67
72
|
signal?: AbortSignal;
|
|
68
73
|
eventSink?: RuntimeEventSink;
|
|
69
74
|
onPromptsChanged?: (prompts: readonly ClaudeMcpPromptDefinition[]) => void;
|
|
75
|
+
onInstructionsChanged?: (instructions: readonly ClaudeMcpServerInstruction[]) => void;
|
|
70
76
|
authenticateServer?: (name: string) => Promise<void>;
|
|
71
77
|
reloadResources?: () => Promise<readonly ClaudeJsonResource[]>;
|
|
72
78
|
onElicitation?: (request: {
|
|
@@ -93,6 +99,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
|
|
|
93
99
|
private readonly serverClients;
|
|
94
100
|
private readonly reconnectableServers;
|
|
95
101
|
private readonly serverCapabilities;
|
|
102
|
+
private readonly serverInstructions;
|
|
96
103
|
private readonly reconnectingServers;
|
|
97
104
|
private readonly promptOperations;
|
|
98
105
|
private promptResultDirectoryPromise;
|
|
@@ -104,6 +111,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
|
|
|
104
111
|
definitions(): readonly ModelToolDefinition[];
|
|
105
112
|
schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
|
|
106
113
|
serverStatuses(): readonly ClaudeMcpServerStatus[];
|
|
114
|
+
instructions(): readonly ClaudeMcpServerInstruction[];
|
|
107
115
|
inspect(): Promise<readonly ClaudeMcpServerStatus[]>;
|
|
108
116
|
reconnect(name: string): Promise<void>;
|
|
109
117
|
authenticate(name: string): Promise<void>;
|
|
@@ -137,6 +145,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
|
|
|
137
145
|
private ensurePromptConnected;
|
|
138
146
|
private reconnectServer;
|
|
139
147
|
private publishPrompts;
|
|
148
|
+
private publishInstructions;
|
|
140
149
|
private assertOpenGeneration;
|
|
141
150
|
private trackPromptOperation;
|
|
142
151
|
private promptResultDirectory;
|
|
@@ -680,6 +680,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
680
680
|
serverClients = new Map();
|
|
681
681
|
reconnectableServers = new Map();
|
|
682
682
|
serverCapabilities = new Map();
|
|
683
|
+
serverInstructions = new Map();
|
|
683
684
|
reconnectingServers = new Map();
|
|
684
685
|
promptOperations = new Set();
|
|
685
686
|
promptResultDirectoryPromise;
|
|
@@ -732,6 +733,11 @@ export class ClaudeMcpToolRegistry {
|
|
|
732
733
|
status,
|
|
733
734
|
}));
|
|
734
735
|
}
|
|
736
|
+
instructions() {
|
|
737
|
+
return [...this.serverInstructions]
|
|
738
|
+
.map(([server, instructions]) => ({ server, instructions }))
|
|
739
|
+
.sort((left, right) => left.server.localeCompare(right.server));
|
|
740
|
+
}
|
|
735
741
|
async inspect() {
|
|
736
742
|
return this.runtimeStatuses();
|
|
737
743
|
}
|
|
@@ -763,6 +769,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
763
769
|
this.statuses.clear();
|
|
764
770
|
this.reconnectableServers.clear();
|
|
765
771
|
this.serverCapabilities.clear();
|
|
772
|
+
this.serverInstructions.clear();
|
|
766
773
|
const ambientSensitiveValues = sensitiveEnvironmentValues(process.env);
|
|
767
774
|
const warn = (message) => this.options.onWarning?.(redactSensitiveText(message, ambientSensitiveValues));
|
|
768
775
|
await this.configure(this.options.reloadResources
|
|
@@ -962,6 +969,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
962
969
|
await Promise.allSettled([...this.clients].map((client) => client.close()));
|
|
963
970
|
this.clients.clear();
|
|
964
971
|
this.serverClients.clear();
|
|
972
|
+
this.serverInstructions.clear();
|
|
965
973
|
const directory = await this.promptResultDirectoryPromise?.catch(() => undefined);
|
|
966
974
|
if (directory)
|
|
967
975
|
await rm(directory, { recursive: true, force: true });
|
|
@@ -1052,7 +1060,9 @@ export class ClaudeMcpToolRegistry {
|
|
|
1052
1060
|
if (this.closed || this.serverClients.get(serverName) !== client)
|
|
1053
1061
|
return;
|
|
1054
1062
|
this.serverCapabilities.delete(serverName);
|
|
1063
|
+
this.serverInstructions.delete(serverName);
|
|
1055
1064
|
this.statuses.set(serverName, { name: serverName, status: 'failed' });
|
|
1065
|
+
this.publishInstructions();
|
|
1056
1066
|
};
|
|
1057
1067
|
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
1058
1068
|
if (!this.options.onElicitation)
|
|
@@ -1131,6 +1141,13 @@ export class ClaudeMcpToolRegistry {
|
|
|
1131
1141
|
...(capabilities?.resources ? ['resources'] : []),
|
|
1132
1142
|
...(capabilities?.prompts ? ['prompts'] : []),
|
|
1133
1143
|
]);
|
|
1144
|
+
const serverInstruction = client.getInstructions()?.trim();
|
|
1145
|
+
if (serverInstruction) {
|
|
1146
|
+
this.serverInstructions.set(serverName, redactSensitiveText(serverInstruction, sensitiveValues));
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
this.serverInstructions.delete(serverName);
|
|
1150
|
+
}
|
|
1134
1151
|
for (const [name, tool] of connectedTools)
|
|
1135
1152
|
this.connectedTools.set(name, tool);
|
|
1136
1153
|
if (capabilities?.resources) {
|
|
@@ -1157,6 +1174,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
1157
1174
|
status: 'connected',
|
|
1158
1175
|
statusDetail: 'connected',
|
|
1159
1176
|
});
|
|
1177
|
+
this.publishInstructions();
|
|
1160
1178
|
}
|
|
1161
1179
|
catch (error) {
|
|
1162
1180
|
await client.close().catch(() => undefined);
|
|
@@ -1383,6 +1401,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
1383
1401
|
this.resourceServers.delete(serverName);
|
|
1384
1402
|
this.promptServers.delete(serverName);
|
|
1385
1403
|
this.serverCapabilities.delete(serverName);
|
|
1404
|
+
this.serverInstructions.delete(serverName);
|
|
1386
1405
|
await this.connectServer(serverName, reconnectable.config, reconnectable.sensitiveValues, expectedGeneration);
|
|
1387
1406
|
if (this.statuses.get(serverName)?.status !== 'connected') {
|
|
1388
1407
|
throw new Error(`MCP server ${serverName} could not reconnect`);
|
|
@@ -1393,6 +1412,10 @@ export class ClaudeMcpToolRegistry {
|
|
|
1393
1412
|
if (!this.closed)
|
|
1394
1413
|
this.options.onPromptsChanged?.(this.prompts());
|
|
1395
1414
|
}
|
|
1415
|
+
publishInstructions() {
|
|
1416
|
+
if (!this.closed)
|
|
1417
|
+
this.options.onInstructionsChanged?.(this.instructions());
|
|
1418
|
+
}
|
|
1396
1419
|
assertOpenGeneration(expectedGeneration) {
|
|
1397
1420
|
if (this.closed || expectedGeneration !== this.generation) {
|
|
1398
1421
|
throw new Error('MCP registry is closed');
|
|
@@ -555,6 +555,20 @@ function serializeMessages(messages) {
|
|
|
555
555
|
}
|
|
556
556
|
return { system: system.join('\n\n'), messages: serialized };
|
|
557
557
|
}
|
|
558
|
+
function validateStableSystemPrefix(request) {
|
|
559
|
+
const count = request.stableSystemMessageCount;
|
|
560
|
+
if (count === undefined)
|
|
561
|
+
return;
|
|
562
|
+
const systemMessageCount = request.messages.filter((message) => message.role === 'system').length;
|
|
563
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > systemMessageCount) {
|
|
564
|
+
throw new Error('Stable system message count must identify a valid system-message prefix');
|
|
565
|
+
}
|
|
566
|
+
if (request.messages
|
|
567
|
+
.slice(0, count)
|
|
568
|
+
.some((message) => message.role !== 'system')) {
|
|
569
|
+
throw new Error('Stable system message count must identify a valid system-message prefix');
|
|
570
|
+
}
|
|
571
|
+
}
|
|
558
572
|
export class AnthropicCompatibleProvider {
|
|
559
573
|
options;
|
|
560
574
|
capabilities;
|
|
@@ -609,6 +623,7 @@ export class AnthropicCompatibleProvider {
|
|
|
609
623
|
this.maxErrorBodyBytes = options.maxErrorBodyBytes ?? 64 * 1024;
|
|
610
624
|
}
|
|
611
625
|
async *complete(request) {
|
|
626
|
+
validateStableSystemPrefix(request);
|
|
612
627
|
if (request.webSearch && !this.capabilities.webSearch) {
|
|
613
628
|
throw new Error('Provider does not support web search');
|
|
614
629
|
}
|