@enderfga/claw-orchestrator 7.2.0 → 7.4.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/README.md +4 -3
- package/configs/autoloop-planner-prompt.md +7 -0
- package/dist/bin/mcp-server.js +1 -0
- package/dist/bin/mcp-server.js.map +1 -1
- package/dist/src/agy-conversation.d.ts +9 -0
- package/dist/src/agy-conversation.js +15 -0
- package/dist/src/agy-conversation.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +7 -0
- package/dist/src/autoloop/dispatcher.js +95 -10
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/planner-tools.d.ts +13 -12
- package/dist/src/autoloop/planner-tools.js +203 -126
- package/dist/src/autoloop/planner-tools.js.map +1 -1
- package/dist/src/handoff.d.ts +80 -0
- package/dist/src/handoff.js +154 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/openai-compat.d.ts +1 -0
- package/dist/src/openai-compat.js +1 -1
- package/dist/src/openai-compat.js.map +1 -1
- package/dist/src/persistent-agy-session.js +43 -18
- package/dist/src/persistent-agy-session.js.map +1 -1
- package/dist/src/session-manager.d.ts +36 -0
- package/dist/src/session-manager.js +78 -1
- package/dist/src/session-manager.js.map +1 -1
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/skills/SKILL.md +3 -3
- package/skills/references/autoloop.md +28 -0
- package/skills/references/multi-engine.md +27 -9
- package/skills/references/sessions.md +47 -0
- package/skills/references/tools.md +21 -1
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* ```
|
|
11
11
|
*
|
|
12
12
|
* After each Planner turn, the dispatcher scans the reply for these blocks,
|
|
13
|
-
* validates
|
|
14
|
-
* runner-state mutations. Multiple blocks per turn are allowed
|
|
15
|
-
*
|
|
13
|
+
* validates the complete batch, and translates it into runner-queue messages
|
|
14
|
+
* or direct runner-state mutations. Multiple blocks per turn are allowed, but
|
|
15
|
+
* no effect is applied unless every block is valid.
|
|
16
16
|
*
|
|
17
17
|
* The naming is stable so a future MCP-based implementation can swap the
|
|
18
18
|
* parser for real tool dispatch without changing any Planner-facing
|
|
@@ -47,9 +47,161 @@ export function parsePlannerReply(reply) {
|
|
|
47
47
|
});
|
|
48
48
|
return { calls, cleaned_reply: cleaned.trim(), parse_errors };
|
|
49
49
|
}
|
|
50
|
+
const PUSH_LEVELS = new Set(['info', 'warn', 'decision', 'error']);
|
|
51
|
+
const PUSH_CHANNELS = new Set(['auto', 'wechat', 'webchat', 'both', 'email']);
|
|
52
|
+
function optionalStringArray(value, field) {
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return [];
|
|
55
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
|
|
56
|
+
throw new Error(`${field} must be an array of strings`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function prepareDirective(raw, tool) {
|
|
61
|
+
const goal = raw.goal;
|
|
62
|
+
if (typeof goal !== 'string' || !goal.trim())
|
|
63
|
+
throw new Error(`${tool} requires \`goal\``);
|
|
64
|
+
const maxAttempts = raw.max_attempts ?? 1;
|
|
65
|
+
if (typeof maxAttempts !== 'number' || !Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
66
|
+
throw new Error(`${tool} max_attempts must be a positive integer`);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
goal,
|
|
70
|
+
constraints: optionalStringArray(raw.constraints, `${tool} constraints`),
|
|
71
|
+
success_criteria: optionalStringArray(raw.success_criteria, `${tool} success_criteria`),
|
|
72
|
+
max_attempts: maxAttempts,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function preparePlannerCall(call, iter) {
|
|
76
|
+
if (typeof call.args !== 'object' || call.args === null || Array.isArray(call.args)) {
|
|
77
|
+
throw new Error(`${call.tool} requires an object \`args\``);
|
|
78
|
+
}
|
|
79
|
+
switch (call.tool) {
|
|
80
|
+
case 'notify_user': {
|
|
81
|
+
const { level, summary, detail, channel } = call.args;
|
|
82
|
+
if (typeof summary !== 'string' || !summary.trim())
|
|
83
|
+
throw new Error('notify_user requires `summary`');
|
|
84
|
+
if (level !== undefined && (typeof level !== 'string' || !PUSH_LEVELS.has(level))) {
|
|
85
|
+
throw new Error('notify_user has invalid `level`');
|
|
86
|
+
}
|
|
87
|
+
if (detail !== undefined && typeof detail !== 'string')
|
|
88
|
+
throw new Error('notify_user `detail` must be a string');
|
|
89
|
+
if (channel !== undefined && (typeof channel !== 'string' || !PUSH_CHANNELS.has(channel))) {
|
|
90
|
+
throw new Error('notify_user has invalid `channel`');
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
call,
|
|
94
|
+
message: Msg.pushUser(iter, {
|
|
95
|
+
level: level ?? 'info',
|
|
96
|
+
summary,
|
|
97
|
+
detail,
|
|
98
|
+
channel: channel ?? 'auto',
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
case 'spawn_subagents': {
|
|
103
|
+
const raw = call.args;
|
|
104
|
+
if ('coder_custom_engine' in raw ||
|
|
105
|
+
'reviewer_custom_engine' in raw ||
|
|
106
|
+
'coderCustomEngine' in raw ||
|
|
107
|
+
'reviewerCustomEngine' in raw ||
|
|
108
|
+
'customEngine' in raw) {
|
|
109
|
+
throw new Error('spawn_subagents cannot include custom engine config; configure it at autoloop_start');
|
|
110
|
+
}
|
|
111
|
+
for (const field of ['coder_engine', 'reviewer_engine']) {
|
|
112
|
+
const value = raw[field];
|
|
113
|
+
if (value !== undefined && (typeof value !== 'string' || !ENGINE_TYPES.includes(value))) {
|
|
114
|
+
throw new Error(`spawn_subagents ${field} has unknown engine '${String(value)}'`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const field of ['coder_model', 'reviewer_model']) {
|
|
118
|
+
const value = raw[field];
|
|
119
|
+
if (value !== undefined && typeof value !== 'string') {
|
|
120
|
+
throw new Error(`spawn_subagents ${field} must be a string`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const args = {};
|
|
124
|
+
if (raw.coder_engine !== undefined)
|
|
125
|
+
args.coder_engine = raw.coder_engine;
|
|
126
|
+
if (raw.coder_model !== undefined)
|
|
127
|
+
args.coder_model = raw.coder_model;
|
|
128
|
+
if (raw.reviewer_engine !== undefined)
|
|
129
|
+
args.reviewer_engine = raw.reviewer_engine;
|
|
130
|
+
if (raw.reviewer_model !== undefined)
|
|
131
|
+
args.reviewer_model = raw.reviewer_model;
|
|
132
|
+
let message;
|
|
133
|
+
if (raw.initial_directive !== undefined) {
|
|
134
|
+
if (typeof raw.initial_directive !== 'object' ||
|
|
135
|
+
raw.initial_directive === null ||
|
|
136
|
+
Array.isArray(raw.initial_directive)) {
|
|
137
|
+
throw new Error('spawn_subagents initial_directive must be an object');
|
|
138
|
+
}
|
|
139
|
+
const directive = prepareDirective(raw.initial_directive, 'spawn_subagents initial_directive');
|
|
140
|
+
args.initial_directive = directive;
|
|
141
|
+
message = Msg.directive(iter, directive);
|
|
142
|
+
}
|
|
143
|
+
return { call, message, spawnArgs: args };
|
|
144
|
+
}
|
|
145
|
+
case 'send_directive': {
|
|
146
|
+
const directive = prepareDirective(call.args, 'send_directive');
|
|
147
|
+
return { call, message: Msg.directive(iter, directive) };
|
|
148
|
+
}
|
|
149
|
+
case 'pause_loop': {
|
|
150
|
+
const reason = call.args.reason ?? 'planner-pause';
|
|
151
|
+
if (typeof reason !== 'string')
|
|
152
|
+
throw new Error('pause_loop `reason` must be a string');
|
|
153
|
+
return { call, message: Msg.pause(iter, { reason }) };
|
|
154
|
+
}
|
|
155
|
+
case 'resume_loop':
|
|
156
|
+
return { call, message: Msg.resume(iter) };
|
|
157
|
+
case 'terminate': {
|
|
158
|
+
const reason = call.args.reason ?? 'planner-terminate';
|
|
159
|
+
if (typeof reason !== 'string')
|
|
160
|
+
throw new Error('terminate `reason` must be a string');
|
|
161
|
+
return { call, message: Msg.terminate(iter, { reason }) };
|
|
162
|
+
}
|
|
163
|
+
case 'update_push_policy':
|
|
164
|
+
return { call, policyDelta: call.args };
|
|
165
|
+
case 'write_plan': {
|
|
166
|
+
const { content, commit_message } = call.args;
|
|
167
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
168
|
+
throw new Error('write_plan requires non-empty `content` (full plan.md body)');
|
|
169
|
+
}
|
|
170
|
+
if (commit_message !== undefined && typeof commit_message !== 'string') {
|
|
171
|
+
throw new Error('write_plan `commit_message` must be a string');
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
call,
|
|
175
|
+
artifact: { file: 'plan.md', content, commitMessage: commit_message },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
case 'write_goal': {
|
|
179
|
+
const { content, commit_message } = call.args;
|
|
180
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
181
|
+
throw new Error('write_goal requires non-empty `content` (full goal.json body)');
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
JSON.parse(content);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new Error(`write_goal content is not valid JSON: ${error.message}`);
|
|
188
|
+
}
|
|
189
|
+
if (commit_message !== undefined && typeof commit_message !== 'string') {
|
|
190
|
+
throw new Error('write_goal `commit_message` must be a string');
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
call,
|
|
194
|
+
artifact: { file: 'goal.json', content, commitMessage: commit_message },
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
default:
|
|
198
|
+
throw new Error(`unknown planner tool: ${call.tool}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
50
201
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
202
|
+
* Preflight a complete batch, materialize all artifact writes as one effect,
|
|
203
|
+
* then apply policy/spawn effects. Spawn is deliberately last, independent of
|
|
204
|
+
* block order, so roles never start against a partial artifact set.
|
|
53
205
|
*
|
|
54
206
|
* Note: notify_user / pause_loop / resume_loop / terminate / send_directive
|
|
55
207
|
* become v2 messages and flow through the runner's normal queue (so policy,
|
|
@@ -57,134 +209,59 @@ export function parsePlannerReply(reply) {
|
|
|
57
209
|
* push-policy mutation are direct side effects.
|
|
58
210
|
*/
|
|
59
211
|
export async function applyPlannerToolCalls(calls, fx, iter) {
|
|
60
|
-
const emitted_messages = [];
|
|
61
212
|
const errors = [];
|
|
213
|
+
const prepared = [];
|
|
62
214
|
for (const call of calls) {
|
|
63
215
|
try {
|
|
64
|
-
|
|
65
|
-
case 'notify_user': {
|
|
66
|
-
const { level, summary, detail, channel } = call.args;
|
|
67
|
-
if (!summary)
|
|
68
|
-
throw new Error('notify_user requires `summary`');
|
|
69
|
-
emitted_messages.push(Msg.pushUser(iter, {
|
|
70
|
-
level: level ?? 'info',
|
|
71
|
-
summary,
|
|
72
|
-
detail,
|
|
73
|
-
channel: channel ?? 'auto',
|
|
74
|
-
}));
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case 'spawn_subagents': {
|
|
78
|
-
const raw = call.args;
|
|
79
|
-
if ('coder_custom_engine' in raw ||
|
|
80
|
-
'reviewer_custom_engine' in raw ||
|
|
81
|
-
'coderCustomEngine' in raw ||
|
|
82
|
-
'reviewerCustomEngine' in raw ||
|
|
83
|
-
'customEngine' in raw) {
|
|
84
|
-
throw new Error('spawn_subagents cannot include custom engine config; configure it at autoloop_start');
|
|
85
|
-
}
|
|
86
|
-
for (const field of ['coder_engine', 'reviewer_engine']) {
|
|
87
|
-
const value = raw[field];
|
|
88
|
-
if (value !== undefined && (typeof value !== 'string' || !ENGINE_TYPES.includes(value))) {
|
|
89
|
-
throw new Error(`spawn_subagents ${field} has unknown engine '${String(value)}'`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
for (const field of ['coder_model', 'reviewer_model']) {
|
|
93
|
-
const value = raw[field];
|
|
94
|
-
if (value !== undefined && typeof value !== 'string') {
|
|
95
|
-
throw new Error(`spawn_subagents ${field} must be a string`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const args = {};
|
|
99
|
-
if (raw.coder_engine !== undefined)
|
|
100
|
-
args.coder_engine = raw.coder_engine;
|
|
101
|
-
if (raw.coder_model !== undefined)
|
|
102
|
-
args.coder_model = raw.coder_model;
|
|
103
|
-
if (raw.reviewer_engine !== undefined)
|
|
104
|
-
args.reviewer_engine = raw.reviewer_engine;
|
|
105
|
-
if (raw.reviewer_model !== undefined)
|
|
106
|
-
args.reviewer_model = raw.reviewer_model;
|
|
107
|
-
if (raw.initial_directive !== undefined) {
|
|
108
|
-
args.initial_directive = raw.initial_directive;
|
|
109
|
-
}
|
|
110
|
-
await fx.spawnSubagents(args);
|
|
111
|
-
// If caller asked for an initial directive, fire it via the runner queue.
|
|
112
|
-
const init = args.initial_directive;
|
|
113
|
-
if (init?.goal) {
|
|
114
|
-
emitted_messages.push(Msg.directive(iter, {
|
|
115
|
-
goal: init.goal,
|
|
116
|
-
constraints: init.constraints ?? [],
|
|
117
|
-
success_criteria: init.success_criteria ?? [],
|
|
118
|
-
max_attempts: init.max_attempts ?? 1,
|
|
119
|
-
}));
|
|
120
|
-
}
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case 'send_directive': {
|
|
124
|
-
const { goal, constraints, success_criteria, max_attempts } = call.args;
|
|
125
|
-
if (!goal)
|
|
126
|
-
throw new Error('send_directive requires `goal`');
|
|
127
|
-
emitted_messages.push(Msg.directive(iter, {
|
|
128
|
-
goal,
|
|
129
|
-
constraints: constraints ?? [],
|
|
130
|
-
success_criteria: success_criteria ?? [],
|
|
131
|
-
max_attempts: max_attempts ?? 1,
|
|
132
|
-
}));
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
case 'pause_loop': {
|
|
136
|
-
emitted_messages.push(Msg.pause(iter, {
|
|
137
|
-
reason: call.args.reason ?? 'planner-pause',
|
|
138
|
-
}));
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
case 'resume_loop': {
|
|
142
|
-
emitted_messages.push(Msg.resume(iter));
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
case 'terminate': {
|
|
146
|
-
emitted_messages.push(Msg.terminate(iter, {
|
|
147
|
-
reason: call.args.reason ?? 'planner-terminate',
|
|
148
|
-
}));
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
case 'update_push_policy': {
|
|
152
|
-
fx.updatePushPolicy(call.args);
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
case 'write_plan': {
|
|
156
|
-
const { content, commit_message } = call.args;
|
|
157
|
-
if (typeof content !== 'string' || !content.trim()) {
|
|
158
|
-
throw new Error('write_plan requires non-empty `content` (full plan.md body)');
|
|
159
|
-
}
|
|
160
|
-
await fx.writePlanFile('plan.md', content, commit_message);
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
case 'write_goal': {
|
|
164
|
-
const { content, commit_message } = call.args;
|
|
165
|
-
if (typeof content !== 'string' || !content.trim()) {
|
|
166
|
-
throw new Error('write_goal requires non-empty `content` (full goal.json body)');
|
|
167
|
-
}
|
|
168
|
-
// Validate it parses as JSON — goal.json must be machine-readable.
|
|
169
|
-
// The error message includes the parse position so the Planner can
|
|
170
|
-
// self-correct on the next turn.
|
|
171
|
-
try {
|
|
172
|
-
JSON.parse(content);
|
|
173
|
-
}
|
|
174
|
-
catch (e) {
|
|
175
|
-
throw new Error(`write_goal content is not valid JSON: ${e.message}`);
|
|
176
|
-
}
|
|
177
|
-
await fx.writePlanFile('goal.json', content, commit_message);
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
default:
|
|
181
|
-
errors.push({ tool: call.tool, error: `unknown planner tool: ${call.tool}` });
|
|
182
|
-
}
|
|
216
|
+
prepared.push(preparePlannerCall(call, iter));
|
|
183
217
|
}
|
|
184
218
|
catch (err) {
|
|
185
219
|
errors.push({ tool: call.tool, error: err.message });
|
|
186
220
|
}
|
|
187
221
|
}
|
|
188
|
-
|
|
222
|
+
if (errors.length > 0)
|
|
223
|
+
return { emitted_messages: [], errors };
|
|
224
|
+
for (const singleton of ['write_plan', 'write_goal', 'spawn_subagents']) {
|
|
225
|
+
if (prepared.filter(({ call }) => call.tool === singleton).length > 1) {
|
|
226
|
+
errors.push({ tool: singleton, error: `duplicate ${singleton} control in one Planner batch` });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (errors.length > 0)
|
|
230
|
+
return { emitted_messages: [], errors };
|
|
231
|
+
const artifacts = prepared.flatMap(({ artifact }) => (artifact ? [artifact] : []));
|
|
232
|
+
if (artifacts.length > 0) {
|
|
233
|
+
try {
|
|
234
|
+
await fx.writePlanFiles(artifacts);
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
return {
|
|
238
|
+
emitted_messages: [],
|
|
239
|
+
errors: [{ tool: artifacts.map(({ file }) => file).join(','), error: err.message }],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const entry of prepared) {
|
|
244
|
+
if (!entry.policyDelta)
|
|
245
|
+
continue;
|
|
246
|
+
try {
|
|
247
|
+
fx.updatePushPolicy(entry.policyDelta);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
return { emitted_messages: [], errors: [{ tool: entry.call.tool, error: err.message }] };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const spawnCall = prepared.find(({ spawnArgs }) => spawnArgs !== undefined);
|
|
254
|
+
if (spawnCall?.spawnArgs) {
|
|
255
|
+
try {
|
|
256
|
+
await fx.spawnSubagents(spawnCall.spawnArgs);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
return { emitted_messages: [], errors: [{ tool: spawnCall.call.tool, error: err.message }] };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
emitted_messages: prepared.flatMap(({ message }) => (message ? [message] : [])),
|
|
264
|
+
errors,
|
|
265
|
+
};
|
|
189
266
|
}
|
|
190
267
|
//# sourceMappingURL=planner-tools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-tools.js","sourceRoot":"","sources":["../../../src/autoloop/planner-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAC5D,OAAO,EAA2B,GAAG,EAAoC,MAAM,eAAe,CAAC;AA0B/F,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,MAAM,YAAY,GAAkD,EAAE,CAAC;IACvE,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE;QAC/D,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAoB,CAAC;YAC1D,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACjG,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;gBACjF,OAAO,EAAE,CAAC,CAAC,2DAA2D;YACxE,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,YAAY,EAAE,CAAC;AAChE,CAAC;
|
|
1
|
+
{"version":3,"file":"planner-tools.js","sourceRoot":"","sources":["../../../src/autoloop/planner-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session handoff — carry a conversation from one engine to another.
|
|
3
|
+
*
|
|
4
|
+
* A session's context lives inside its engine: Claude Code's transcript, a Codex
|
|
5
|
+
* thread, an Antigravity conversation. None of them can resume another's, so the
|
|
6
|
+
* only engine-agnostic way across is to replay the conversation as text into a
|
|
7
|
+
* new session on the target engine. This module keeps the record that makes that
|
|
8
|
+
* possible and renders it.
|
|
9
|
+
*
|
|
10
|
+
* Why a record of its own rather than `getHistory()`: that is a ring buffer of
|
|
11
|
+
* the last `MAX_HISTORY_ITEMS` engine events, in each engine's own shape. On a
|
|
12
|
+
* long session the opening request has already fallen out of it — and the
|
|
13
|
+
* opening request is the one thing a handoff must not lose, because every later
|
|
14
|
+
* turn is an answer to it.
|
|
15
|
+
*
|
|
16
|
+
* Why not write the target engine's native session files instead: those formats
|
|
17
|
+
* are internal and undocumented, change between releases, and live in the
|
|
18
|
+
* user's own session stores. Replaying text touches none of them, works for
|
|
19
|
+
* every engine including custom ones, and leaves the source session untouched.
|
|
20
|
+
* What it cannot carry is the same thing no vendor format carries either: the
|
|
21
|
+
* source engine's hidden reasoning.
|
|
22
|
+
*/
|
|
23
|
+
export interface TranscriptEntry {
|
|
24
|
+
role: 'user' | 'assistant';
|
|
25
|
+
text: string;
|
|
26
|
+
}
|
|
27
|
+
export interface Transcript {
|
|
28
|
+
entries: TranscriptEntry[];
|
|
29
|
+
/** Entries evicted from just after the opening request to keep the record bounded. */
|
|
30
|
+
omittedAfterFirst: number;
|
|
31
|
+
/** Total characters held in `entries`. */
|
|
32
|
+
chars: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What one session's record may hold. Larger than any render needs — the render
|
|
36
|
+
* budget below decides what is sent — so that the newest turns are always
|
|
37
|
+
* complete. Bounded because every session keeps one, for as long as it lives.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_TRANSCRIPT_CHARS = 1000000;
|
|
40
|
+
/**
|
|
41
|
+
* Default size of a rendered handoff, in characters (~60k tokens). Sent once, to
|
|
42
|
+
* a session that then holds the conversation itself, so it can be far larger
|
|
43
|
+
* than the per-turn replay in openai-compat — but it has to fit the smallest
|
|
44
|
+
* window among the engines it may be sent to.
|
|
45
|
+
*/
|
|
46
|
+
export declare const DEFAULT_HANDOFF_CHARS = 240000;
|
|
47
|
+
export declare const MIN_HANDOFF_CHARS = 4000;
|
|
48
|
+
export declare function newTranscript(): Transcript;
|
|
49
|
+
export declare function cloneTranscript(t: Transcript): Transcript;
|
|
50
|
+
/**
|
|
51
|
+
* Append one side of an exchange. Past the cap, entries are evicted from just
|
|
52
|
+
* after the opening request — the oldest middle of the conversation — never the
|
|
53
|
+
* request itself and never the newest turns.
|
|
54
|
+
*/
|
|
55
|
+
export declare function recordExchange(t: Transcript, role: TranscriptEntry['role'], text: string): void;
|
|
56
|
+
export interface HandoffSource {
|
|
57
|
+
engine: string;
|
|
58
|
+
model?: string;
|
|
59
|
+
cwd: string;
|
|
60
|
+
}
|
|
61
|
+
export interface HandoffRender {
|
|
62
|
+
/** The block to put in front of the first message the target session receives. */
|
|
63
|
+
text: string;
|
|
64
|
+
/** Turns rendered, the pinned opening request included. */
|
|
65
|
+
turns: number;
|
|
66
|
+
/** Turns left out, whether the record had already evicted them or the budget did. */
|
|
67
|
+
omitted: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Render a transcript for a new session on another engine.
|
|
71
|
+
*
|
|
72
|
+
* Everything is sent when it fits. When it does not, the opening request is
|
|
73
|
+
* pinned and the newest turns fill what is left, with one line saying how many
|
|
74
|
+
* turns in between were left out: the request says what the work is for, the
|
|
75
|
+
* newest turns say where it stands, and the middle is what can be re-derived
|
|
76
|
+
* from the workspace. Every turn's text is fenced the way openai-compat fences
|
|
77
|
+
* replayed turns, so a transcript that contains the block's own tags cannot
|
|
78
|
+
* close the block early and speak as a different role.
|
|
79
|
+
*/
|
|
80
|
+
export declare function renderHandoff(t: Transcript, src: HandoffSource, maxChars?: number): HandoffRender;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session handoff — carry a conversation from one engine to another.
|
|
3
|
+
*
|
|
4
|
+
* A session's context lives inside its engine: Claude Code's transcript, a Codex
|
|
5
|
+
* thread, an Antigravity conversation. None of them can resume another's, so the
|
|
6
|
+
* only engine-agnostic way across is to replay the conversation as text into a
|
|
7
|
+
* new session on the target engine. This module keeps the record that makes that
|
|
8
|
+
* possible and renders it.
|
|
9
|
+
*
|
|
10
|
+
* Why a record of its own rather than `getHistory()`: that is a ring buffer of
|
|
11
|
+
* the last `MAX_HISTORY_ITEMS` engine events, in each engine's own shape. On a
|
|
12
|
+
* long session the opening request has already fallen out of it — and the
|
|
13
|
+
* opening request is the one thing a handoff must not lose, because every later
|
|
14
|
+
* turn is an answer to it.
|
|
15
|
+
*
|
|
16
|
+
* Why not write the target engine's native session files instead: those formats
|
|
17
|
+
* are internal and undocumented, change between releases, and live in the
|
|
18
|
+
* user's own session stores. Replaying text touches none of them, works for
|
|
19
|
+
* every engine including custom ones, and leaves the source session untouched.
|
|
20
|
+
* What it cannot carry is the same thing no vendor format carries either: the
|
|
21
|
+
* source engine's hidden reasoning.
|
|
22
|
+
*/
|
|
23
|
+
import { fenceHistoryTags } from './openai-compat.js';
|
|
24
|
+
/**
|
|
25
|
+
* What one session's record may hold. Larger than any render needs — the render
|
|
26
|
+
* budget below decides what is sent — so that the newest turns are always
|
|
27
|
+
* complete. Bounded because every session keeps one, for as long as it lives.
|
|
28
|
+
*/
|
|
29
|
+
export const MAX_TRANSCRIPT_CHARS = 1_000_000;
|
|
30
|
+
/**
|
|
31
|
+
* Default size of a rendered handoff, in characters (~60k tokens). Sent once, to
|
|
32
|
+
* a session that then holds the conversation itself, so it can be far larger
|
|
33
|
+
* than the per-turn replay in openai-compat — but it has to fit the smallest
|
|
34
|
+
* window among the engines it may be sent to.
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_HANDOFF_CHARS = 240_000;
|
|
37
|
+
export const MIN_HANDOFF_CHARS = 4_000;
|
|
38
|
+
/** The opening request is kept whole up to this, and head-truncated past it. */
|
|
39
|
+
const PINNED_OPENING_CHARS = 8_000;
|
|
40
|
+
/** Least room worth starting a turn in; with less, the turn is dropped. */
|
|
41
|
+
const MIN_TURN_CHARS = 200;
|
|
42
|
+
const ELISION = '\n[… truncated for length …]';
|
|
43
|
+
export function newTranscript() {
|
|
44
|
+
return { entries: [], omittedAfterFirst: 0, chars: 0 };
|
|
45
|
+
}
|
|
46
|
+
export function cloneTranscript(t) {
|
|
47
|
+
return { entries: t.entries.map((e) => ({ ...e })), omittedAfterFirst: t.omittedAfterFirst, chars: t.chars };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Append one side of an exchange. Past the cap, entries are evicted from just
|
|
51
|
+
* after the opening request — the oldest middle of the conversation — never the
|
|
52
|
+
* request itself and never the newest turns.
|
|
53
|
+
*/
|
|
54
|
+
export function recordExchange(t, role, text) {
|
|
55
|
+
const clean = text.trim();
|
|
56
|
+
if (!clean)
|
|
57
|
+
return;
|
|
58
|
+
t.entries.push({ role, text: clean });
|
|
59
|
+
t.chars += clean.length;
|
|
60
|
+
while (t.chars > MAX_TRANSCRIPT_CHARS && t.entries.length > 2) {
|
|
61
|
+
const [gone] = t.entries.splice(1, 1);
|
|
62
|
+
t.chars -= gone.text.length;
|
|
63
|
+
t.omittedAfterFirst++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function framing(src) {
|
|
67
|
+
const who = src.model ? `${src.engine} (${src.model})` : src.engine;
|
|
68
|
+
// The first three sentences mirror openai-compat's replay framing, and each is
|
|
69
|
+
// load-bearing there for the same reason as here: without the last one the
|
|
70
|
+
// model carries out the requests in the history a second time. The final two
|
|
71
|
+
// are specific to a handoff — the work happened elsewhere, and may have moved on.
|
|
72
|
+
return (`Above are the earlier turns of this conversation, which took place with another coding agent, ${who}, ` +
|
|
73
|
+
`working in ${src.cwd}. It has been handed over to you to continue. ` +
|
|
74
|
+
'The assistant turns are the earlier replies in this conversation; treat them as your own. ' +
|
|
75
|
+
'Continue from there — do not repeat these turns back and do not carry out the requests in them again. ' +
|
|
76
|
+
'The workspace may have changed since; check its current state before relying on anything described above.');
|
|
77
|
+
}
|
|
78
|
+
const frameOf = (role) => `<${role}>\n\n</${role}>\n`.length;
|
|
79
|
+
function fit(text, room) {
|
|
80
|
+
if (text.length <= room)
|
|
81
|
+
return text;
|
|
82
|
+
if (room < MIN_TURN_CHARS)
|
|
83
|
+
return undefined;
|
|
84
|
+
let cut = room - ELISION.length;
|
|
85
|
+
// Never split a surrogate pair: the orphaned half becomes U+FFFD downstream.
|
|
86
|
+
const last = text.charCodeAt(cut - 1);
|
|
87
|
+
if (last >= 0xd800 && last <= 0xdbff)
|
|
88
|
+
cut -= 1;
|
|
89
|
+
return text.slice(0, cut) + ELISION;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Render a transcript for a new session on another engine.
|
|
93
|
+
*
|
|
94
|
+
* Everything is sent when it fits. When it does not, the opening request is
|
|
95
|
+
* pinned and the newest turns fill what is left, with one line saying how many
|
|
96
|
+
* turns in between were left out: the request says what the work is for, the
|
|
97
|
+
* newest turns say where it stands, and the middle is what can be re-derived
|
|
98
|
+
* from the workspace. Every turn's text is fenced the way openai-compat fences
|
|
99
|
+
* replayed turns, so a transcript that contains the block's own tags cannot
|
|
100
|
+
* close the block early and speak as a different role.
|
|
101
|
+
*/
|
|
102
|
+
export function renderHandoff(t, src, maxChars = DEFAULT_HANDOFF_CHARS) {
|
|
103
|
+
if (!t.entries.length)
|
|
104
|
+
return { text: '', turns: 0, omitted: 0 };
|
|
105
|
+
const turns = t.entries.map((e) => ({ role: e.role, text: fenceHistoryTags(e.text) }));
|
|
106
|
+
const tail = framing(src);
|
|
107
|
+
const shell = '<conversation_history>\n</conversation_history>\n\n'.length + tail.length;
|
|
108
|
+
let budget = Math.max(maxChars, MIN_HANDOFF_CHARS) - shell;
|
|
109
|
+
const renderedLen = turns.reduce((n, x) => n + frameOf(x.role) + x.text.length, 0);
|
|
110
|
+
let kept;
|
|
111
|
+
let omitted = t.omittedAfterFirst;
|
|
112
|
+
let markerAfterFirst = t.omittedAfterFirst > 0;
|
|
113
|
+
if (renderedLen <= budget) {
|
|
114
|
+
kept = turns;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const opening = turns[0];
|
|
118
|
+
// A quarter of the budget, capped. MIN_HANDOFF_CHARS keeps this above
|
|
119
|
+
// MIN_TURN_CHARS, so the opening request always renders, if only its head.
|
|
120
|
+
const pinnedRoom = Math.min(PINNED_OPENING_CHARS, Math.floor(budget / 4)) - frameOf(opening.role);
|
|
121
|
+
const pinned = { role: opening.role, text: fit(opening.text, pinnedRoom) ?? '' };
|
|
122
|
+
budget -= frameOf(pinned.role) + pinned.text.length;
|
|
123
|
+
const markerReserve = '[… 99999 earlier turns omitted …]\n'.length;
|
|
124
|
+
budget -= markerReserve;
|
|
125
|
+
const newest = [];
|
|
126
|
+
for (let i = turns.length - 1; i >= 1; i--) {
|
|
127
|
+
const room = budget - frameOf(turns[i].role);
|
|
128
|
+
const text = fit(turns[i].text, room);
|
|
129
|
+
if (text === undefined)
|
|
130
|
+
break;
|
|
131
|
+
newest.unshift({ role: turns[i].role, text });
|
|
132
|
+
budget -= frameOf(turns[i].role) + text.length;
|
|
133
|
+
if (text !== turns[i].text)
|
|
134
|
+
break; // the boundary turn was cut; nothing older fits
|
|
135
|
+
}
|
|
136
|
+
omitted += turns.length - 1 - newest.length;
|
|
137
|
+
markerAfterFirst = omitted > 0;
|
|
138
|
+
kept = [pinned, ...newest];
|
|
139
|
+
}
|
|
140
|
+
const lines = [];
|
|
141
|
+
kept.forEach((turn, i) => {
|
|
142
|
+
if (turn.text)
|
|
143
|
+
lines.push(`<${turn.role}>\n${turn.text}\n</${turn.role}>`);
|
|
144
|
+
if (i === 0 && markerAfterFirst) {
|
|
145
|
+
lines.push(`[… ${omitted} earlier turn${omitted === 1 ? '' : 's'} omitted …]`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
text: `<conversation_history>\n${lines.join('\n')}\n</conversation_history>\n\n${tail}`,
|
|
150
|
+
turns: kept.filter((k) => k.text).length,
|
|
151
|
+
omitted,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.js","sourceRoot":"","sources":["../../src/handoff.ts"],"names":[],"mappings":"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"}
|