pi-ultracode 0.1.0 → 0.1.2
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 +401 -148
- package/extensions/ultracode.ts +134 -16
- package/package.json +22 -1
- package/src/commands.ts +32 -84
- package/src/index.ts +20 -0
- package/src/mode.ts +445 -34
- package/src/prompts.ts +2 -2
- package/src/thinking.ts +27 -0
- package/src/workflow/agent-runner.ts +903 -53
- package/src/workflow/agent-types.ts +3 -6
- package/src/workflow/display-text.ts +461 -0
- package/src/workflow/display.ts +188 -55
- package/src/workflow/journal.ts +0 -0
- package/src/workflow/registry.ts +106 -10
- package/src/workflow/run-details.ts +1276 -0
- package/src/workflow/runtime.ts +189 -38
- package/src/workflow/tool.ts +285 -47
- package/src/workflow/workflow-overlay.ts +719 -0
- package/types/workflow.d.ts +1 -1
package/src/workflow/runtime.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import vm from "node:vm";
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
14
|
import * as fs from "node:fs";
|
|
14
15
|
import * as os from "node:os";
|
|
15
16
|
import * as path from "node:path";
|
|
@@ -18,7 +19,17 @@ import { parseWorkflowScript, type WorkflowMeta } from "./parser.ts";
|
|
|
18
19
|
// loader ("WorkflowAgentRunner is not a constructor"). Tests inject a runner, so
|
|
19
20
|
// they never construct this class; production builds it via getRunner().
|
|
20
21
|
import { WorkflowAgentRunner } from "./agent-runner.ts";
|
|
21
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
AgentActivityInput,
|
|
24
|
+
AgentRunResult,
|
|
25
|
+
AgentTelemetryEvent,
|
|
26
|
+
AgentUsage,
|
|
27
|
+
ModelLike,
|
|
28
|
+
ModelRegistryLike,
|
|
29
|
+
ModelRuntimeLike,
|
|
30
|
+
ThinkingLevel,
|
|
31
|
+
} from "./agent-runner.ts";
|
|
32
|
+
import { safeDisplayText } from "./display-text.ts";
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
35
|
* A frozen copy of `Math` with `random` replaced by a throwing function. Workflow
|
|
@@ -54,7 +65,7 @@ export function createDeterministicMath(): Record<string, unknown> {
|
|
|
54
65
|
|
|
55
66
|
const DETERMINISTIC_MATH = createDeterministicMath();
|
|
56
67
|
import { discoverAgentTypes, resolveAgentType, type AgentTypeDef } from "./agent-types.ts";
|
|
57
|
-
import { agentCallKey, RunJournal } from "./journal.ts";
|
|
68
|
+
import { agentCallKey, RunJournal, type JournalAgentRecord } from "./journal.ts";
|
|
58
69
|
import {
|
|
59
70
|
applyPatch,
|
|
60
71
|
captureWorktreeDiff,
|
|
@@ -70,18 +81,18 @@ import {
|
|
|
70
81
|
const MAX_CONCURRENCY = 16;
|
|
71
82
|
const MAX_AGENTS_PER_RUN = 1000;
|
|
72
83
|
const MAX_ITEMS_PER_CALL = 4096;
|
|
84
|
+
export const MAX_WORKFLOW_LOGS = 256;
|
|
85
|
+
export const WORKFLOW_LOG_OMITTED_TEXT = "additional workflow logs omitted";
|
|
73
86
|
|
|
74
87
|
export interface AgentEventBase {
|
|
75
88
|
id: number;
|
|
76
89
|
label: string;
|
|
77
90
|
phase?: string;
|
|
91
|
+
workflowPath?: string[];
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
/** Live activity observed inside a running subagent
|
|
81
|
-
export
|
|
82
|
-
kind: "text" | "thinking" | "tool";
|
|
83
|
-
detail?: string;
|
|
84
|
-
}
|
|
94
|
+
/** Live activity observed inside a running subagent. */
|
|
95
|
+
export type AgentActivityEvent = AgentEventBase & AgentActivityInput;
|
|
85
96
|
|
|
86
97
|
export interface WorkflowRunOptions {
|
|
87
98
|
cwd?: string;
|
|
@@ -89,7 +100,10 @@ export interface WorkflowRunOptions {
|
|
|
89
100
|
signal?: AbortSignal;
|
|
90
101
|
concurrency?: number;
|
|
91
102
|
tokenBudget?: number | null;
|
|
103
|
+
/** Synchronous extension facade used for model selection. */
|
|
92
104
|
modelRegistry?: ModelRegistryLike;
|
|
105
|
+
/** Canonical model/auth runtime shared across child sessions. */
|
|
106
|
+
modelRuntime?: ModelRuntimeLike;
|
|
93
107
|
model?: ModelLike;
|
|
94
108
|
thinkingLevel?: ThinkingLevel;
|
|
95
109
|
/** Inject a runner (tests). */
|
|
@@ -99,9 +113,27 @@ export interface WorkflowRunOptions {
|
|
|
99
113
|
loadSavedWorkflow?: (nameOrRef: string | { scriptPath: string }) => { meta: WorkflowMeta; body: string };
|
|
100
114
|
onLog?: (message: string) => void;
|
|
101
115
|
onPhase?: (title: string) => void;
|
|
102
|
-
onAgentStart?: (event: AgentEventBase & {
|
|
103
|
-
|
|
116
|
+
onAgentStart?: (event: AgentEventBase & {
|
|
117
|
+
prompt: string;
|
|
118
|
+
cached: boolean;
|
|
119
|
+
modelPattern?: string;
|
|
120
|
+
requestedEffort?: ThinkingLevel;
|
|
121
|
+
agentType?: string;
|
|
122
|
+
isolation?: string;
|
|
123
|
+
structuredOutput?: boolean;
|
|
124
|
+
cachedRecord?: JournalAgentRecord;
|
|
125
|
+
}) => void;
|
|
126
|
+
onAgentEnd?: (event: AgentEventBase & {
|
|
127
|
+
result: unknown;
|
|
128
|
+
status: "done" | "error";
|
|
129
|
+
usage?: AgentUsage;
|
|
130
|
+
modelId?: string;
|
|
131
|
+
effort?: ThinkingLevel;
|
|
132
|
+
error?: string;
|
|
133
|
+
cachedRecord?: JournalAgentRecord;
|
|
134
|
+
}) => void;
|
|
104
135
|
onAgentActivity?: (event: AgentActivityEvent) => void;
|
|
136
|
+
onAgentTelemetry?: (event: AgentEventBase & AgentTelemetryEvent) => void;
|
|
105
137
|
}
|
|
106
138
|
|
|
107
139
|
export interface WorkflowRunResult<T = unknown> {
|
|
@@ -112,6 +144,10 @@ export interface WorkflowRunResult<T = unknown> {
|
|
|
112
144
|
agentCount: number;
|
|
113
145
|
cachedCount: number;
|
|
114
146
|
spentTokens: number;
|
|
147
|
+
/** Actual input+output tokens incurred by live child sessions. */
|
|
148
|
+
newTokens: number;
|
|
149
|
+
/** Original input+output usage represented by cached replayed tasks. */
|
|
150
|
+
replayedTokens: number;
|
|
115
151
|
durationMs: number;
|
|
116
152
|
}
|
|
117
153
|
|
|
@@ -121,7 +157,9 @@ interface RuntimeState {
|
|
|
121
157
|
phases: string[];
|
|
122
158
|
agentCount: number; // number of agent() invocations (for ids / cap)
|
|
123
159
|
cachedCount: number;
|
|
124
|
-
spent: number; // real output tokens
|
|
160
|
+
spent: number; // real output tokens (budget compatibility)
|
|
161
|
+
newTokens: number;
|
|
162
|
+
replayedTokens: number;
|
|
125
163
|
}
|
|
126
164
|
|
|
127
165
|
export async function runWorkflow<T = unknown>(
|
|
@@ -144,12 +182,22 @@ export async function runWorkflow<T = unknown>(
|
|
|
144
182
|
agentCount: runtime.state.agentCount,
|
|
145
183
|
cachedCount: runtime.state.cachedCount,
|
|
146
184
|
spentTokens: runtime.state.spent,
|
|
185
|
+
newTokens: runtime.state.newTokens,
|
|
186
|
+
replayedTokens: runtime.state.replayedTokens,
|
|
147
187
|
durationMs: Date.now() - started,
|
|
148
188
|
};
|
|
149
189
|
}
|
|
150
190
|
|
|
151
191
|
class Runtime {
|
|
152
|
-
readonly state: RuntimeState = {
|
|
192
|
+
readonly state: RuntimeState = {
|
|
193
|
+
logs: [],
|
|
194
|
+
phases: [],
|
|
195
|
+
agentCount: 0,
|
|
196
|
+
cachedCount: 0,
|
|
197
|
+
spent: 0,
|
|
198
|
+
newTokens: 0,
|
|
199
|
+
replayedTokens: 0,
|
|
200
|
+
};
|
|
153
201
|
private readonly options: WorkflowRunOptions;
|
|
154
202
|
private readonly cwd: string;
|
|
155
203
|
private runnerInstance: { run: WorkflowAgentRunner["run"] } | undefined;
|
|
@@ -158,7 +206,7 @@ class Runtime {
|
|
|
158
206
|
private readonly pending = new Set<Promise<unknown>>();
|
|
159
207
|
private readonly tokenBudget: number | null;
|
|
160
208
|
private readonly applyLock = new Mutex();
|
|
161
|
-
private
|
|
209
|
+
private readonly executionContext = new AsyncLocalStorage<{ workflowPath: string[]; depth: number; currentPhase?: string }>();
|
|
162
210
|
|
|
163
211
|
constructor(options: WorkflowRunOptions) {
|
|
164
212
|
this.options = options;
|
|
@@ -190,6 +238,7 @@ class Runtime {
|
|
|
190
238
|
this.runnerInstance = new WorkflowAgentRunner({
|
|
191
239
|
cwd: this.cwd,
|
|
192
240
|
modelRegistry: this.options.modelRegistry,
|
|
241
|
+
modelRuntime: this.options.modelRuntime,
|
|
193
242
|
model: this.options.model,
|
|
194
243
|
thinkingLevel: this.options.thinkingLevel,
|
|
195
244
|
});
|
|
@@ -201,15 +250,17 @@ class Runtime {
|
|
|
201
250
|
async runBody(body: string, args: unknown, depth: number, name: string): Promise<unknown> {
|
|
202
251
|
const context = vm.createContext(this.buildSandbox(args));
|
|
203
252
|
const wrapped = `(async () => {\n${body}\n})()`;
|
|
204
|
-
|
|
253
|
+
const parent = this.executionContext.getStore();
|
|
254
|
+
const parentPath = parent?.workflowPath ?? [];
|
|
255
|
+
const execution = { workflowPath: [...parentPath, name || `workflow-${depth}`], depth, currentPhase: parent?.currentPhase };
|
|
256
|
+
return this.executionContext.run(
|
|
257
|
+
execution,
|
|
258
|
+
async () => await new vm.Script(wrapped, { filename: `${name || "workflow"}.js` }).runInContext(context),
|
|
259
|
+
);
|
|
205
260
|
}
|
|
206
261
|
|
|
207
262
|
private buildSandbox(args: unknown): Record<string, unknown> {
|
|
208
|
-
const log = (message: unknown) =>
|
|
209
|
-
const text = String(message);
|
|
210
|
-
this.state.logs.push(text);
|
|
211
|
-
this.options.onLog?.(text);
|
|
212
|
-
};
|
|
263
|
+
const log = (message: unknown) => this.logLine(String(message));
|
|
213
264
|
return {
|
|
214
265
|
agent: this.agent.bind(this),
|
|
215
266
|
parallel: this.parallel.bind(this),
|
|
@@ -248,6 +299,8 @@ class Runtime {
|
|
|
248
299
|
private phase(title: unknown): void {
|
|
249
300
|
const text = requireString(title, "phase title");
|
|
250
301
|
this.state.currentPhase = text;
|
|
302
|
+
const execution = this.executionContext.getStore();
|
|
303
|
+
if (execution) execution.currentPhase = text;
|
|
251
304
|
if (!this.state.phases.includes(text)) this.state.phases.push(text);
|
|
252
305
|
this.options.onPhase?.(text);
|
|
253
306
|
}
|
|
@@ -259,7 +312,7 @@ class Runtime {
|
|
|
259
312
|
}
|
|
260
313
|
const prompt = requireString(promptValue, "agent prompt");
|
|
261
314
|
const opts = normalizeAgentOptions(optionsValue);
|
|
262
|
-
const assignedPhase = opts.phase ?? this.state.currentPhase;
|
|
315
|
+
const assignedPhase = opts.phase ?? this.executionContext.getStore()?.currentPhase ?? this.state.currentPhase;
|
|
263
316
|
|
|
264
317
|
const seq = ++this.state.agentCount;
|
|
265
318
|
if (seq > MAX_AGENTS_PER_RUN) {
|
|
@@ -267,6 +320,7 @@ class Runtime {
|
|
|
267
320
|
}
|
|
268
321
|
const id = seq;
|
|
269
322
|
const label = opts.label?.trim() || defaultLabel(assignedPhase, id);
|
|
323
|
+
const workflowPath = [...(this.executionContext.getStore()?.workflowPath ?? [])];
|
|
270
324
|
const key = agentCallKey(prompt, { ...opts, phase: assignedPhase });
|
|
271
325
|
|
|
272
326
|
// Resume: cached prefix replay.
|
|
@@ -274,13 +328,62 @@ class Runtime {
|
|
|
274
328
|
if (cached) {
|
|
275
329
|
this.state.cachedCount++;
|
|
276
330
|
this.state.spent += cached.outputTokens ?? 0;
|
|
277
|
-
this.
|
|
278
|
-
|
|
331
|
+
this.state.replayedTokens += cached.totalTokens ?? 0;
|
|
332
|
+
const cachedUsage: AgentUsage = {
|
|
333
|
+
inputTokens: cached.inputTokens,
|
|
334
|
+
outputTokens: cached.outputTokens ?? 0,
|
|
335
|
+
totalTokens: cached.totalTokens ?? 0,
|
|
336
|
+
cacheReadTokens: cached.cacheReadTokens,
|
|
337
|
+
cacheWriteTokens: cached.cacheWriteTokens,
|
|
338
|
+
cost: cached.cost ?? 0,
|
|
339
|
+
turns: cached.turns,
|
|
340
|
+
toolUses: cached.toolUses,
|
|
341
|
+
retries: cached.retries,
|
|
342
|
+
compactions: cached.compactions,
|
|
343
|
+
};
|
|
344
|
+
this.options.onAgentStart?.({
|
|
345
|
+
id,
|
|
346
|
+
label,
|
|
347
|
+
phase: assignedPhase,
|
|
348
|
+
workflowPath,
|
|
349
|
+
prompt,
|
|
350
|
+
cached: true,
|
|
351
|
+
modelPattern: opts.model,
|
|
352
|
+
requestedEffort: this.options.thinkingLevel,
|
|
353
|
+
agentType: opts.agentType,
|
|
354
|
+
isolation: opts.isolation,
|
|
355
|
+
structuredOutput: opts.schema != null,
|
|
356
|
+
cachedRecord: cached,
|
|
357
|
+
});
|
|
358
|
+
this.options.onAgentEnd?.({
|
|
359
|
+
id,
|
|
360
|
+
label,
|
|
361
|
+
phase: assignedPhase,
|
|
362
|
+
workflowPath,
|
|
363
|
+
result: cached.value,
|
|
364
|
+
status: "done",
|
|
365
|
+
usage: cachedUsage,
|
|
366
|
+
modelId: cached.modelId,
|
|
367
|
+
effort: cached.effort,
|
|
368
|
+
cachedRecord: cached,
|
|
369
|
+
});
|
|
279
370
|
return cached.value;
|
|
280
371
|
}
|
|
281
372
|
|
|
282
373
|
const run = this.limiter(async () => {
|
|
283
|
-
this.options.onAgentStart?.({
|
|
374
|
+
this.options.onAgentStart?.({
|
|
375
|
+
id,
|
|
376
|
+
label,
|
|
377
|
+
phase: assignedPhase,
|
|
378
|
+
workflowPath,
|
|
379
|
+
prompt,
|
|
380
|
+
cached: false,
|
|
381
|
+
modelPattern: opts.model,
|
|
382
|
+
requestedEffort: this.options.thinkingLevel,
|
|
383
|
+
agentType: opts.agentType,
|
|
384
|
+
isolation: opts.isolation,
|
|
385
|
+
structuredOutput: opts.schema != null,
|
|
386
|
+
});
|
|
284
387
|
let worktree: Worktree | undefined;
|
|
285
388
|
let keepWorktree = false;
|
|
286
389
|
try {
|
|
@@ -294,7 +397,11 @@ class Runtime {
|
|
|
294
397
|
const runner = this.getRunner();
|
|
295
398
|
const onActivity: ((e: AgentActivityInput) => void) | undefined = this.options.onAgentActivity
|
|
296
399
|
? (e: AgentActivityInput) =>
|
|
297
|
-
this.options.onAgentActivity!({ id, label, phase: assignedPhase, ...e })
|
|
400
|
+
this.options.onAgentActivity!({ id, label, phase: assignedPhase, workflowPath, ...e })
|
|
401
|
+
: undefined;
|
|
402
|
+
const onTelemetry: ((e: AgentTelemetryEvent) => void) | undefined = this.options.onAgentTelemetry
|
|
403
|
+
? (e: AgentTelemetryEvent) =>
|
|
404
|
+
this.options.onAgentTelemetry!({ id, label, phase: assignedPhase, workflowPath, ...e })
|
|
298
405
|
: undefined;
|
|
299
406
|
const agentStartedAt = Date.now();
|
|
300
407
|
const result: AgentRunResult = await runner.run({
|
|
@@ -307,28 +414,71 @@ class Runtime {
|
|
|
307
414
|
agentTypeDef,
|
|
308
415
|
cwd: worktree?.agentCwd,
|
|
309
416
|
onActivity,
|
|
417
|
+
onTelemetry,
|
|
310
418
|
});
|
|
311
419
|
this.throwIfAborted();
|
|
312
420
|
|
|
313
421
|
if (worktree) keepWorktree = await this.integrateWorktree(worktree, id, label);
|
|
314
422
|
|
|
315
|
-
|
|
423
|
+
const inputTokens = result.usage.inputTokens
|
|
424
|
+
?? Math.max(0, result.usage.totalTokens - result.usage.outputTokens);
|
|
425
|
+
const usage: AgentUsage = {
|
|
426
|
+
...result.usage,
|
|
427
|
+
inputTokens,
|
|
428
|
+
totalTokens: inputTokens + result.usage.outputTokens,
|
|
429
|
+
};
|
|
430
|
+
this.state.spent += usage.outputTokens;
|
|
431
|
+
this.state.newTokens += usage.totalTokens;
|
|
316
432
|
this.options.journal?.recordAgent({
|
|
317
433
|
seq,
|
|
318
434
|
key,
|
|
319
435
|
label,
|
|
320
436
|
value: result.value,
|
|
321
|
-
|
|
437
|
+
inputTokens: usage.inputTokens,
|
|
438
|
+
outputTokens: usage.outputTokens,
|
|
439
|
+
totalTokens: usage.totalTokens,
|
|
440
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
441
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
442
|
+
cost: usage.cost,
|
|
443
|
+
turns: usage.turns,
|
|
444
|
+
toolUses: usage.toolUses,
|
|
445
|
+
retries: usage.retries,
|
|
446
|
+
compactions: usage.compactions,
|
|
447
|
+
requestedModelId: opts.model,
|
|
448
|
+
requestedEffort: this.options.thinkingLevel,
|
|
449
|
+
modelId: result.modelId,
|
|
450
|
+
effort: result.effort,
|
|
451
|
+
agentType: opts.agentType,
|
|
452
|
+
isolation: opts.isolation,
|
|
453
|
+
structuredOutput: opts.schema != null,
|
|
322
454
|
startedAt: agentStartedAt,
|
|
323
455
|
durationMs: Date.now() - agentStartedAt,
|
|
324
456
|
});
|
|
325
|
-
this.options.onAgentEnd?.({
|
|
457
|
+
this.options.onAgentEnd?.({
|
|
458
|
+
id,
|
|
459
|
+
label,
|
|
460
|
+
phase: assignedPhase,
|
|
461
|
+
workflowPath,
|
|
462
|
+
result: result.value,
|
|
463
|
+
status: "done",
|
|
464
|
+
usage,
|
|
465
|
+
modelId: result.modelId,
|
|
466
|
+
effort: result.effort,
|
|
467
|
+
});
|
|
326
468
|
return result.value;
|
|
327
469
|
} catch (error) {
|
|
328
470
|
if (this.options.signal?.aborted) throw error;
|
|
329
471
|
const message = error instanceof Error ? error.message : String(error);
|
|
330
472
|
this.logLine(`agent ${label} failed: ${message}`);
|
|
331
|
-
this.options.onAgentEnd?.({
|
|
473
|
+
this.options.onAgentEnd?.({
|
|
474
|
+
id,
|
|
475
|
+
label,
|
|
476
|
+
phase: assignedPhase,
|
|
477
|
+
workflowPath,
|
|
478
|
+
result: null,
|
|
479
|
+
status: "error",
|
|
480
|
+
error: message,
|
|
481
|
+
});
|
|
332
482
|
return null;
|
|
333
483
|
} finally {
|
|
334
484
|
if (worktree && !keepWorktree) {
|
|
@@ -399,20 +549,15 @@ class Runtime {
|
|
|
399
549
|
|
|
400
550
|
private async workflow(nameOrRef: unknown, args: unknown): Promise<unknown> {
|
|
401
551
|
this.throwIfAborted();
|
|
402
|
-
|
|
552
|
+
const depth = this.executionContext.getStore()?.depth ?? 0;
|
|
553
|
+
if (depth >= 1) {
|
|
403
554
|
throw new Error("workflow() nesting is one level deep only; cannot call workflow() inside a child workflow");
|
|
404
555
|
}
|
|
405
556
|
const ref = normalizeWorkflowRef(nameOrRef);
|
|
406
557
|
const loader = this.options.loadSavedWorkflow ?? ((r) => loadSavedWorkflowFromDisk(r, this.cwd));
|
|
407
558
|
const { meta, body } = loader(ref);
|
|
408
|
-
this.
|
|
409
|
-
|
|
410
|
-
this.options.onLog?.(`▸ nested workflow: ${meta.name}`);
|
|
411
|
-
const value = await this.runBody(body, args, this.depth, meta.name);
|
|
412
|
-
return value;
|
|
413
|
-
} finally {
|
|
414
|
-
this.depth--;
|
|
415
|
-
}
|
|
559
|
+
this.logLine(`▸ nested workflow: ${meta.name}`);
|
|
560
|
+
return this.runBody(body, args, depth + 1, meta.name);
|
|
416
561
|
}
|
|
417
562
|
|
|
418
563
|
private tryCreateWorktree(index: number): Worktree | undefined {
|
|
@@ -515,8 +660,14 @@ class Runtime {
|
|
|
515
660
|
}
|
|
516
661
|
|
|
517
662
|
private logLine(text: string): void {
|
|
518
|
-
this.state.logs.
|
|
519
|
-
|
|
663
|
+
if (this.state.logs.length > MAX_WORKFLOW_LOGS) return;
|
|
664
|
+
const safe = safeDisplayText(text, 512);
|
|
665
|
+
if (!safe) return;
|
|
666
|
+
const entry = this.state.logs.length === MAX_WORKFLOW_LOGS
|
|
667
|
+
? WORKFLOW_LOG_OMITTED_TEXT
|
|
668
|
+
: safe;
|
|
669
|
+
this.state.logs.push(entry);
|
|
670
|
+
this.options.onLog?.(entry);
|
|
520
671
|
}
|
|
521
672
|
}
|
|
522
673
|
|