@noetic-tools/context 1.0.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/LICENSE +201 -0
- package/README.md +28 -0
- package/dist/context/budget.d.ts +39 -0
- package/dist/context/budget.js +183 -0
- package/dist/context/exec-context-factory.d.ts +24 -0
- package/dist/context/exec-context-factory.js +30 -0
- package/dist/context/function-call-utils.d.ts +6 -0
- package/dist/context/function-call-utils.js +27 -0
- package/dist/context/layer-api.d.ts +11 -0
- package/dist/context/layer-api.js +91 -0
- package/dist/context/layer-lifecycle.d.ts +239 -0
- package/dist/context/layer-lifecycle.js +800 -0
- package/dist/context/layer-provides.d.ts +32 -0
- package/dist/context/layer-provides.js +33 -0
- package/dist/context/layer-usage.d.ts +13 -0
- package/dist/context/layer-usage.js +54 -0
- package/dist/context/layers/durable-task-state.d.ts +107 -0
- package/dist/context/layers/durable-task-state.js +250 -0
- package/dist/context/layers/file-reference.d.ts +55 -0
- package/dist/context/layers/file-reference.js +729 -0
- package/dist/context/layers/history-window.d.ts +31 -0
- package/dist/context/layers/history-window.js +150 -0
- package/dist/context/layers/observational-context.d.ts +56 -0
- package/dist/context/layers/observational-context.js +152 -0
- package/dist/context/layers/plan-context.d.ts +73 -0
- package/dist/context/layers/plan-context.js +749 -0
- package/dist/context/layers/static-content.d.ts +27 -0
- package/dist/context/layers/static-content.js +46 -0
- package/dist/context/layers/steering.d.ts +50 -0
- package/dist/context/layers/steering.js +291 -0
- package/dist/context/layers/temporal-context.d.ts +91 -0
- package/dist/context/layers/temporal-context.js +327 -0
- package/dist/context/layers/tool-context-layer.d.ts +15 -0
- package/dist/context/layers/tool-context-layer.js +43 -0
- package/dist/context/layers/working-memory-context.d.ts +46 -0
- package/dist/context/layers/working-memory-context.js +155 -0
- package/dist/context/projector.d.ts +19 -0
- package/dist/context/projector.js +89 -0
- package/dist/context/scope.d.ts +3 -0
- package/dist/context/scope.js +42 -0
- package/dist/context/storage-batch.d.ts +18 -0
- package/dist/context/storage-batch.js +35 -0
- package/dist/context/strip-unresolved.d.ts +13 -0
- package/dist/context/strip-unresolved.js +35 -0
- package/dist/deprecated.d.ts +43 -0
- package/dist/deprecated.js +28 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +31 -0
- package/package.json +63 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History-window context layer — caps the number of items projected to the LLM
|
|
3
|
+
* without touching `itemLog` storage. Pure read-side projection via the
|
|
4
|
+
* `projectHistory` hook.
|
|
5
|
+
*
|
|
6
|
+
* Algorithm per turn:
|
|
7
|
+
* 1. Slice the last `maxItems`.
|
|
8
|
+
* 2. If the slice lacks a user `message` or an assistant `message`, expand
|
|
9
|
+
* backward until both are present. The cap may be exceeded, but only up to
|
|
10
|
+
* a hard bound (`maxItems * MAX_EXPANSION_MULTIPLE`) — a tool-only burst
|
|
11
|
+
* cannot grow the window back to the start of history.
|
|
12
|
+
* 3. Re-attach a system/anchor message from the head if it fell outside the
|
|
13
|
+
* window, so core instructions survive windowing.
|
|
14
|
+
* 4. Drop any orphan `function_call` / `function_call_output` left at the
|
|
15
|
+
* slice boundary via `stripUnresolvedToolCalls`.
|
|
16
|
+
*/
|
|
17
|
+
import type { ContextLayer } from '@noetic-tools/types';
|
|
18
|
+
/** @public Configuration for the {@link historyWindow} layer. */
|
|
19
|
+
export interface HistoryWindowConfig {
|
|
20
|
+
/** Cap on the number of trailing items projected to the LLM. Defaults to 40. */
|
|
21
|
+
maxItems?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a history-window context layer that caps the trailing items projected
|
|
25
|
+
* to the LLM. Storage stays unbounded; only the wire payload is narrowed.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
* @param config - Optional `{ maxItems }`. Defaults to 40.
|
|
29
|
+
* @returns A `ContextLayer` that contributes only a `projectHistory` hook.
|
|
30
|
+
*/
|
|
31
|
+
export declare function historyWindow(config?: HistoryWindowConfig): ContextLayer<null>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History-window context layer — caps the number of items projected to the LLM
|
|
3
|
+
* without touching `itemLog` storage. Pure read-side projection via the
|
|
4
|
+
* `projectHistory` hook.
|
|
5
|
+
*
|
|
6
|
+
* Algorithm per turn:
|
|
7
|
+
* 1. Slice the last `maxItems`.
|
|
8
|
+
* 2. If the slice lacks a user `message` or an assistant `message`, expand
|
|
9
|
+
* backward until both are present. The cap may be exceeded, but only up to
|
|
10
|
+
* a hard bound (`maxItems * MAX_EXPANSION_MULTIPLE`) — a tool-only burst
|
|
11
|
+
* cannot grow the window back to the start of history.
|
|
12
|
+
* 3. Re-attach a system/anchor message from the head if it fell outside the
|
|
13
|
+
* window, so core instructions survive windowing.
|
|
14
|
+
* 4. Drop any orphan `function_call` / `function_call_output` left at the
|
|
15
|
+
* slice boundary via `stripUnresolvedToolCalls`.
|
|
16
|
+
*/
|
|
17
|
+
import { isAssistantMessage, isUserMessage, NoeticConfigError, Slot } from '@noetic-tools/types';
|
|
18
|
+
import { stripUnresolvedToolCalls } from '../strip-unresolved.js';
|
|
19
|
+
//#region Constants
|
|
20
|
+
const DEFAULT_MAX_ITEMS = 40;
|
|
21
|
+
const MIN_MAX_ITEMS = 2;
|
|
22
|
+
const MAX_MAX_ITEMS = 1e4;
|
|
23
|
+
const HISTORY_WINDOW_SLOT = Slot.PROCEDURAL + 25; // 275 — runs after recall-contributing layers
|
|
24
|
+
// Hard upper bound on how far minimum-exchange expansion may grow the window,
|
|
25
|
+
// expressed as a multiple of maxItems. Without this, a tool-only burst lets the
|
|
26
|
+
// window expand back to the first role-pair, defeating the cap entirely.
|
|
27
|
+
const MAX_EXPANSION_MULTIPLE = 4;
|
|
28
|
+
// How many leading items to scan for a system/anchor message to preserve.
|
|
29
|
+
const ANCHOR_SCAN_LIMIT = 4;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region Helpers
|
|
32
|
+
function validateMaxItems(value) {
|
|
33
|
+
if (!Number.isInteger(value) || value < MIN_MAX_ITEMS || value > MAX_MAX_ITEMS) {
|
|
34
|
+
throw new NoeticConfigError({
|
|
35
|
+
code: 'INVALID_HISTORY_WINDOW_MAX_ITEMS',
|
|
36
|
+
message: `historyWindow: maxItems must be an integer in [${MIN_MAX_ITEMS}, ${MAX_MAX_ITEMS}], got ${value}`,
|
|
37
|
+
hint: 'Pass a sensible cap such as { maxItems: 40 }, or omit the layer to keep history uncapped.',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Walk backwards from the natural slice point until both a user message and
|
|
43
|
+
* an assistant message are present. Returns the index at which the projected
|
|
44
|
+
* window should start in the full `items` array.
|
|
45
|
+
*/
|
|
46
|
+
function expandToMinimumExchange(items, sliceStart) {
|
|
47
|
+
let start = sliceStart;
|
|
48
|
+
let needsUser = true;
|
|
49
|
+
let needsAssistant = true;
|
|
50
|
+
// Pass 1: scan the natural slice for the user/assistant pair. Early-return
|
|
51
|
+
// at the original sliceStart when both roles are already inside.
|
|
52
|
+
for (let i = items.length - 1; i >= start; i--) {
|
|
53
|
+
const item = items[i];
|
|
54
|
+
if (needsUser && isUserMessage(item)) {
|
|
55
|
+
needsUser = false;
|
|
56
|
+
}
|
|
57
|
+
if (needsAssistant && isAssistantMessage(item)) {
|
|
58
|
+
needsAssistant = false;
|
|
59
|
+
}
|
|
60
|
+
if (!needsUser && !needsAssistant) {
|
|
61
|
+
return start;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Pass 2: walk backward past the slice until the missing role(s) are found.
|
|
65
|
+
for (let i = start - 1; i >= 0 && (needsUser || needsAssistant); i--) {
|
|
66
|
+
const item = items[i];
|
|
67
|
+
if (needsUser && isUserMessage(item)) {
|
|
68
|
+
needsUser = false;
|
|
69
|
+
start = i;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (needsAssistant && isAssistantMessage(item)) {
|
|
73
|
+
needsAssistant = false;
|
|
74
|
+
start = i;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return start;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Find a system/anchor message in the leading items that falls outside the
|
|
81
|
+
* projected window (`windowStart`). Returns it so it can be re-attached to the
|
|
82
|
+
* head of the projection; returns null when there is none.
|
|
83
|
+
*/
|
|
84
|
+
function findHeadAnchor(items, windowStart) {
|
|
85
|
+
const limit = Math.min(windowStart, ANCHOR_SCAN_LIMIT);
|
|
86
|
+
for (let i = 0; i < limit; i++) {
|
|
87
|
+
const item = items[i];
|
|
88
|
+
if (item.type === 'message' && item.role === 'system') {
|
|
89
|
+
return item;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region Public API
|
|
96
|
+
/**
|
|
97
|
+
* Creates a history-window context layer that caps the trailing items projected
|
|
98
|
+
* to the LLM. Storage stays unbounded; only the wire payload is narrowed.
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
* @param config - Optional `{ maxItems }`. Defaults to 40.
|
|
102
|
+
* @returns A `ContextLayer` that contributes only a `projectHistory` hook.
|
|
103
|
+
*/
|
|
104
|
+
export function historyWindow(config) {
|
|
105
|
+
const maxItems = config?.maxItems ?? DEFAULT_MAX_ITEMS;
|
|
106
|
+
validateMaxItems(maxItems);
|
|
107
|
+
return {
|
|
108
|
+
id: 'history-window',
|
|
109
|
+
name: 'History Window',
|
|
110
|
+
slot: HISTORY_WINDOW_SLOT,
|
|
111
|
+
scope: 'execution',
|
|
112
|
+
hooks: {
|
|
113
|
+
async init() {
|
|
114
|
+
return {
|
|
115
|
+
state: null,
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
async projectHistory({ items }) {
|
|
119
|
+
if (items.length <= maxItems) {
|
|
120
|
+
return {
|
|
121
|
+
items,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const sliceStart = items.length - maxItems;
|
|
125
|
+
let expandedStart = expandToMinimumExchange(items, sliceStart);
|
|
126
|
+
// Hard upper bound: minimum-exchange expansion (e.g. for a tool-only
|
|
127
|
+
// burst) must never grow the window without limit. Drop the oldest
|
|
128
|
+
// excess so the projected count stays a bounded multiple of maxItems.
|
|
129
|
+
const maxWindow = maxItems * MAX_EXPANSION_MULTIPLE;
|
|
130
|
+
if (items.length - expandedStart > maxWindow) {
|
|
131
|
+
expandedStart = items.length - maxWindow;
|
|
132
|
+
}
|
|
133
|
+
const window = items.slice(expandedStart);
|
|
134
|
+
// Preserve a system/anchor message at the head even when it falls
|
|
135
|
+
// outside the trailing window.
|
|
136
|
+
const anchor = findHeadAnchor(items, expandedStart);
|
|
137
|
+
const cleaned = stripUnresolvedToolCalls(anchor
|
|
138
|
+
? [
|
|
139
|
+
anchor,
|
|
140
|
+
...window,
|
|
141
|
+
]
|
|
142
|
+
: window);
|
|
143
|
+
return {
|
|
144
|
+
items: cleaned,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ObservationalState {
|
|
2
|
+
observations: string[];
|
|
3
|
+
buffer: string[];
|
|
4
|
+
bufferTokens: number;
|
|
5
|
+
version: number;
|
|
6
|
+
}
|
|
7
|
+
export type ObserverFn = (buffer: string[]) => Promise<string[]>;
|
|
8
|
+
export interface ObservationalContextConfig {
|
|
9
|
+
bufferThreshold?: number;
|
|
10
|
+
maxObservations?: number;
|
|
11
|
+
scope?: 'thread' | 'resource';
|
|
12
|
+
observer?: ObserverFn;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a context layer that buffers raw items and distills them into observations when a token threshold is reached.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
* @param config - Optional configuration for buffer threshold, max observations, scope, and observer function.
|
|
19
|
+
* @returns A `ContextLayer` that accumulates and summarizes observations over time.
|
|
20
|
+
*/
|
|
21
|
+
export declare function observationalContext(config?: ObservationalContextConfig): {
|
|
22
|
+
id: 'observational-context';
|
|
23
|
+
name: string;
|
|
24
|
+
slot: 200;
|
|
25
|
+
scope: "resource" | "thread";
|
|
26
|
+
budget: {
|
|
27
|
+
min: number;
|
|
28
|
+
max: number;
|
|
29
|
+
};
|
|
30
|
+
timeouts: {
|
|
31
|
+
store: number;
|
|
32
|
+
onItemAppend: number;
|
|
33
|
+
};
|
|
34
|
+
hooks: {
|
|
35
|
+
init({ storage }: import("@noetic-tools/types").InitParams): Promise<{
|
|
36
|
+
state: ObservationalState;
|
|
37
|
+
}>;
|
|
38
|
+
recall({ state, budget }: import("@noetic-tools/types").RecallParams<ObservationalState>): Promise<{
|
|
39
|
+
items: import("@noetic-tools/types").InputMessageItem[];
|
|
40
|
+
tokenCount: number;
|
|
41
|
+
} | null>;
|
|
42
|
+
store({ newItems, state }: import("@noetic-tools/types").StoreParams<ObservationalState>): Promise<{
|
|
43
|
+
state: ObservationalState;
|
|
44
|
+
}>;
|
|
45
|
+
onItemAppend({ items, state }: import("@noetic-tools/types").OnItemAppendParams<ObservationalState>): Promise<{
|
|
46
|
+
items: import("@noetic-tools/types").Item[];
|
|
47
|
+
state?: undefined;
|
|
48
|
+
} | {
|
|
49
|
+
items: import("@noetic-tools/types").Item[];
|
|
50
|
+
state: ObservationalState;
|
|
51
|
+
}>;
|
|
52
|
+
onSpawn({ parentState }: import("@noetic-tools/types").SpawnParams<ObservationalState>): Promise<{
|
|
53
|
+
childState: ObservationalState;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { collectInputText, collectOutputText, createMessage, estimateTokens, Slot, } from '@noetic-tools/types';
|
|
2
|
+
const DEFAULT_BUFFER_THRESHOLD_TOKENS = 2_000;
|
|
3
|
+
const DEFAULT_MAX_OBSERVATIONS = 50;
|
|
4
|
+
function emptyObservationalState() {
|
|
5
|
+
return {
|
|
6
|
+
observations: [],
|
|
7
|
+
buffer: [],
|
|
8
|
+
bufferTokens: 0,
|
|
9
|
+
version: 0,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Appends `texts` into the layer buffer and, once the token threshold is crossed,
|
|
14
|
+
* distills the buffer into observations. Shared by `store` (assistant output) and
|
|
15
|
+
* `onItemAppend` (user/tool input).
|
|
16
|
+
*/
|
|
17
|
+
async function accumulate(s, texts, cfg) {
|
|
18
|
+
const newBuffer = [
|
|
19
|
+
...s.buffer,
|
|
20
|
+
...texts,
|
|
21
|
+
];
|
|
22
|
+
const newTokens = texts.reduce((sum, t) => sum + estimateTokens(t), 0);
|
|
23
|
+
const totalBufferTokens = s.bufferTokens + newTokens;
|
|
24
|
+
if (totalBufferTokens >= cfg.threshold) {
|
|
25
|
+
const distilled = cfg.observer
|
|
26
|
+
? await cfg.observer(newBuffer)
|
|
27
|
+
: [
|
|
28
|
+
`Processed ${newBuffer.length} items`,
|
|
29
|
+
];
|
|
30
|
+
const newObservations = [
|
|
31
|
+
...s.observations,
|
|
32
|
+
...distilled,
|
|
33
|
+
].slice(-cfg.maxObs);
|
|
34
|
+
return {
|
|
35
|
+
observations: newObservations,
|
|
36
|
+
buffer: [],
|
|
37
|
+
bufferTokens: 0,
|
|
38
|
+
version: s.version + 1,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...s,
|
|
43
|
+
buffer: newBuffer,
|
|
44
|
+
bufferTokens: totalBufferTokens,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Render observations as a tagged bullet list. */
|
|
48
|
+
function renderObservations(observations) {
|
|
49
|
+
const bullets = observations.map((o) => `- ${o}`).join('\n');
|
|
50
|
+
return `<observations>\n${bullets}\n</observations>`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Render the most recent observations that fit within `budget` tokens (rendered
|
|
54
|
+
* output included). Drops oldest observations first; returns null if not even
|
|
55
|
+
* the single newest observation fits.
|
|
56
|
+
*/
|
|
57
|
+
function renderObservationsWithinBudget(observations, budget) {
|
|
58
|
+
for (let start = 0; start < observations.length; start++) {
|
|
59
|
+
const text = renderObservations(observations.slice(start));
|
|
60
|
+
if (estimateTokens(text) <= budget) {
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a context layer that buffers raw items and distills them into observations when a token threshold is reached.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
* @param config - Optional configuration for buffer threshold, max observations, scope, and observer function.
|
|
71
|
+
* @returns A `ContextLayer` that accumulates and summarizes observations over time.
|
|
72
|
+
*/
|
|
73
|
+
export function observationalContext(config) {
|
|
74
|
+
const maxObs = config?.maxObservations ?? DEFAULT_MAX_OBSERVATIONS;
|
|
75
|
+
const threshold = config?.bufferThreshold ?? DEFAULT_BUFFER_THRESHOLD_TOKENS;
|
|
76
|
+
const observer = config?.observer;
|
|
77
|
+
return {
|
|
78
|
+
id: 'observational-context',
|
|
79
|
+
name: 'Observational Context',
|
|
80
|
+
slot: Slot.OBSERVATIONS,
|
|
81
|
+
scope: config?.scope ?? 'resource',
|
|
82
|
+
budget: {
|
|
83
|
+
min: 500,
|
|
84
|
+
max: 2_500,
|
|
85
|
+
},
|
|
86
|
+
timeouts: {
|
|
87
|
+
store: 60_000,
|
|
88
|
+
// onItemAppend runs the same LLM-backed accumulate/distill path as
|
|
89
|
+
// store — it needs the same headroom, not the 5s pipeline default.
|
|
90
|
+
onItemAppend: 60_000,
|
|
91
|
+
},
|
|
92
|
+
hooks: {
|
|
93
|
+
async init({ storage }) {
|
|
94
|
+
const saved = await storage.get('state');
|
|
95
|
+
return {
|
|
96
|
+
state: saved ?? emptyObservationalState(),
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
async recall({ state, budget }) {
|
|
100
|
+
if (!state?.observations?.length) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
// Trim to the most recent observations that fit within the token budget.
|
|
104
|
+
const text = renderObservationsWithinBudget(state.observations, budget);
|
|
105
|
+
if (text === null) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
items: [
|
|
110
|
+
createMessage(text, 'developer'),
|
|
111
|
+
],
|
|
112
|
+
tokenCount: estimateTokens(text),
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
// Captures assistant output text.
|
|
116
|
+
async store({ newItems, state }) {
|
|
117
|
+
const s = state ?? emptyObservationalState();
|
|
118
|
+
const texts = collectOutputText(newItems);
|
|
119
|
+
return {
|
|
120
|
+
state: await accumulate(s, texts, {
|
|
121
|
+
threshold,
|
|
122
|
+
maxObs,
|
|
123
|
+
observer,
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
// Captures user input and tool output text (pass-through; no transform).
|
|
128
|
+
async onItemAppend({ items, state }) {
|
|
129
|
+
const s = state ?? emptyObservationalState();
|
|
130
|
+
const texts = collectInputText(items);
|
|
131
|
+
if (texts.length === 0) {
|
|
132
|
+
return {
|
|
133
|
+
items,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
items,
|
|
138
|
+
state: await accumulate(s, texts, {
|
|
139
|
+
threshold,
|
|
140
|
+
maxObs,
|
|
141
|
+
observer,
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
async onSpawn({ parentState }) {
|
|
146
|
+
return {
|
|
147
|
+
childState: structuredClone(parentState),
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ContextLayer, ContextScope, WorkflowDocument, WorkflowNode } from '@noetic-tools/types';
|
|
2
|
+
export declare const PlanPhase: {
|
|
3
|
+
readonly Idle: 'idle';
|
|
4
|
+
readonly Planning: 'planning';
|
|
5
|
+
readonly Executing: 'executing';
|
|
6
|
+
readonly Completed: 'completed';
|
|
7
|
+
readonly Failed: 'failed';
|
|
8
|
+
};
|
|
9
|
+
export type PlanPhase = (typeof PlanPhase)[keyof typeof PlanPhase];
|
|
10
|
+
export interface PlanExecutionEntry {
|
|
11
|
+
timestamp: number;
|
|
12
|
+
version: number;
|
|
13
|
+
outcome: 'success' | 'failure' | 'aborted';
|
|
14
|
+
}
|
|
15
|
+
export interface PlanState {
|
|
16
|
+
phase: PlanPhase;
|
|
17
|
+
prd: string | null;
|
|
18
|
+
/** The reviewed plan: a complete JSON workflow document. */
|
|
19
|
+
planTree: WorkflowDocument | null;
|
|
20
|
+
/** Named workflows referenced from the tree via `{ kind: 'subflow', ref }` nodes. */
|
|
21
|
+
workflows: Record<string, WorkflowDocument>;
|
|
22
|
+
executionLog: PlanExecutionEntry[];
|
|
23
|
+
version: number;
|
|
24
|
+
/** Identifier of the on-disk plan session (set by `onEnterSession` host callback). */
|
|
25
|
+
planSlug?: string | null;
|
|
26
|
+
}
|
|
27
|
+
/** Host-supplied callback invoked when entering plan mode. Returns a session identifier the host owns (e.g. on-disk dir slug). */
|
|
28
|
+
export type PlanEnterSessionCallback = () => Promise<{
|
|
29
|
+
slug: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Host-supplied callback invoked when the model requests `exitPlanMode` with `action: 'execute'`.
|
|
33
|
+
* Return `{ approved: false }` to keep the layer in `Planning` (e.g. user rejected the plan in the UI).
|
|
34
|
+
*/
|
|
35
|
+
export type PlanExitCallback = (state: PlanState) => Promise<{
|
|
36
|
+
approved: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
export interface PlanContextConfig {
|
|
39
|
+
scope?: ContextScope;
|
|
40
|
+
additionalAllowedTools?: string[];
|
|
41
|
+
maxPrdLength?: number;
|
|
42
|
+
/** Max structural depth (`workflowDepth`) of the plan tree and each named workflow. Default 5. */
|
|
43
|
+
maxDepth?: number;
|
|
44
|
+
/** Max number of named workflows a plan may store. Default 20. */
|
|
45
|
+
maxWorkflows?: number;
|
|
46
|
+
/** Max serialized size (`JSON.stringify` length) of each workflow document. Default 20000. */
|
|
47
|
+
maxWorkflowChars?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Optional profile restricting which `WorkflowNode` kinds the plan tree and
|
|
50
|
+
* named workflows may use. Undefined allows all kinds. Hosts that set this
|
|
51
|
+
* must include `'subflow'` for named workflows to be referenceable.
|
|
52
|
+
*/
|
|
53
|
+
allowedNodeKinds?: WorkflowNode['kind'][];
|
|
54
|
+
/** Extra free-form instructions appended to the planning-phase recall payload. */
|
|
55
|
+
additionalPlanInstructions?: string;
|
|
56
|
+
/** Called once when the layer transitions Idle → Planning. */
|
|
57
|
+
onEnterSession?: PlanEnterSessionCallback;
|
|
58
|
+
/** Called when `exitPlanMode` is requested with `action: 'execute'`. */
|
|
59
|
+
onExit?: PlanExitCallback;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates a plan context layer that manages the PRD authoring and plan execution lifecycle.
|
|
63
|
+
*
|
|
64
|
+
* The plan tree is a JSON `WorkflowDocument`; plans additionally store named
|
|
65
|
+
* workflows referenced from the tree via `subflow` nodes, keeping the reviewed
|
|
66
|
+
* tree small. On approval, hosts execute the plan by feeding `planTree` and
|
|
67
|
+
* `workflows` to the JSON workflow runtime (`parseAndRunWorkflow`).
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
* @param config - Optional configuration for scope, allowed tools, and limits.
|
|
71
|
+
* @returns A `ContextLayer` providing plan mode, PRD storage, and execution tracking.
|
|
72
|
+
*/
|
|
73
|
+
export declare function planContext(config?: PlanContextConfig): ContextLayer<PlanState>;
|