@xfey/tutti 0.1.10 → 0.1.12
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/control-plane/context-sync.js +1 -0
- package/dist/control-plane/event-publishers.js +21 -12
- package/dist/control-plane/execution-status.js +4 -0
- package/dist/control-plane/project-context-bootstrap.d.ts +2 -0
- package/dist/control-plane/project-context-bootstrap.js +1 -0
- package/dist/control-plane/reference-summary-refresh.js +4 -0
- package/dist/control-plane/run-start.js +1 -0
- package/dist/control-plane/task-compile-continuation.js +1 -0
- package/dist/control-plane/task-compile-start.d.ts +2 -1
- package/dist/control-plane/task-compile-start.js +5 -0
- package/dist/control-plane/types.d.ts +2 -1
- package/dist/control-plane/workflows/openai.js +19 -0
- package/dist/control-plane/workflows/types.d.ts +6 -1
- package/dist/procedure-engine/index.d.ts +10 -1
- package/dist/procedure-engine/index.js +34 -4
- package/dist/providers/openai/app-server/json-rpc.d.ts +6 -1
- package/dist/providers/openai/app-server/json-rpc.js +15 -1
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +2 -0
- package/dist/providers/openai/app-server/read-only-procedure.js +13 -1
- package/dist/providers/openai/app-server/runtime-telemetry.d.ts +18 -0
- package/dist/providers/openai/app-server/runtime-telemetry.js +130 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +2 -0
- package/dist/providers/openai/app-server/workspace-write-run.js +13 -1
- package/dist/run-engine/index.d.ts +2 -1
- package/dist/run-engine/index.js +14 -1
- package/dist/run-pipeline/task-run-invocation.js +9 -0
- package/dist/server-shell/cli/args.d.ts +9 -9
- package/dist/server-shell/cli/args.js +31 -31
- package/dist/server-shell/cli/cli.js +27 -27
- package/dist/server-shell/cli/errors.d.ts +2 -1
- package/dist/server-shell/cli/errors.js +55 -12
- package/dist/server-shell/cli/host-lifecycle.js +7 -0
- package/dist/server-shell/cli/launch-command.d.ts +2 -2
- package/dist/server-shell/cli/launch-command.js +25 -11
- package/dist/server-shell/cli/launch.js +0 -3
- package/dist/server-shell/cli/managed-host.d.ts +2 -0
- package/dist/server-shell/cli/managed-host.js +9 -18
- package/dist/server-shell/cli/project-resolver.d.ts +6 -0
- package/dist/server-shell/cli/project-resolver.js +125 -3
- package/dist/server-shell/cli/provider-tui.d.ts +19 -0
- package/dist/server-shell/cli/provider-tui.js +64 -38
- package/dist/server-shell/cli/runtime-commands.d.ts +5 -5
- package/dist/server-shell/cli/runtime-commands.js +68 -25
- package/dist/server-shell/cli/terminal-qr.d.ts +5 -0
- package/dist/server-shell/cli/terminal-qr.js +17 -0
- package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +14 -0
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +14 -0
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +28 -0
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +14 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +50 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.js +25 -0
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
- package/package.json +1 -1
- package/web/assets/index-CDUT1Etl.js +29 -0
- package/web/assets/{index-D8xJY8Gr.css → index-DIQpGeKY.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-BMMQgQjz.js +0 -29
|
@@ -70,6 +70,7 @@ export function startContextSyncAnalysisProcedure(input) {
|
|
|
70
70
|
const output = await runnerResolution.runner.runContextSync?.(workflowInput, {
|
|
71
71
|
workflow_ref: workflowRef,
|
|
72
72
|
activity_ref: context.activity_ref,
|
|
73
|
+
reportProgress: context.reportProgress,
|
|
73
74
|
});
|
|
74
75
|
if (output === undefined) {
|
|
75
76
|
return {
|
|
@@ -135,18 +135,27 @@ export function publishRunTransition(input) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
export function publishProcedureTransition(input) {
|
|
138
|
-
const eventPayload = input.state.
|
|
139
|
-
?
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
?
|
|
144
|
-
: input.state.
|
|
145
|
-
? "needs_human"
|
|
146
|
-
: "failed",
|
|
147
|
-
summary: input.state.activity.summary,
|
|
138
|
+
const eventPayload = input.state.transition_kind === "progress"
|
|
139
|
+
? {
|
|
140
|
+
progress: {
|
|
141
|
+
message: input.state.progress?.summary ?? input.state.activity.summary,
|
|
142
|
+
...(input.state.progress?.phase === undefined
|
|
143
|
+
? {}
|
|
144
|
+
: { phase: input.state.progress.phase }),
|
|
148
145
|
},
|
|
149
|
-
}
|
|
146
|
+
}
|
|
147
|
+
: input.state.activity.status === "running"
|
|
148
|
+
? undefined
|
|
149
|
+
: {
|
|
150
|
+
terminal: {
|
|
151
|
+
result_kind: input.state.activity.status === "finished"
|
|
152
|
+
? "finished"
|
|
153
|
+
: input.state.activity.status === "needs_human"
|
|
154
|
+
? "needs_human"
|
|
155
|
+
: "failed",
|
|
156
|
+
summary: input.state.activity.summary,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
150
159
|
input.events.publish({
|
|
151
160
|
event_type: "activity.event",
|
|
152
161
|
payload: {
|
|
@@ -154,7 +163,7 @@ export function publishProcedureTransition(input) {
|
|
|
154
163
|
event: {
|
|
155
164
|
event_ref: createEventRef(),
|
|
156
165
|
activity_ref: input.state.activity.activity_ref,
|
|
157
|
-
event_kind: activityEventKind(input.state.activity),
|
|
166
|
+
event_kind: activityEventKind(input.state.activity, input.state.transition_kind),
|
|
158
167
|
occurred_at: input.now().toISOString(),
|
|
159
168
|
summary: input.state.activity.summary,
|
|
160
169
|
...(eventPayload === undefined ? {} : { payload: eventPayload }),
|
|
@@ -31,6 +31,10 @@ export function readControlPlaneExecutionStatus(options) {
|
|
|
31
31
|
else if (activeProcedureActivity !== undefined) {
|
|
32
32
|
status.active_activity = activeProcedureActivity;
|
|
33
33
|
}
|
|
34
|
+
const runtime = activeRunStatus.runtime ?? activeStatus.runtime;
|
|
35
|
+
if (runtime !== undefined) {
|
|
36
|
+
status.runtime = runtime;
|
|
37
|
+
}
|
|
34
38
|
appendActiveActivities(status, activeActivities);
|
|
35
39
|
const lastConclusion = activeRunStatus.last_conclusion ?? activeStatus.last_conclusion;
|
|
36
40
|
if (lastConclusion !== undefined) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ActivityRef, WorkflowInvocationRef } from "@tutti/shared/ids";
|
|
2
2
|
import type { ScratchpadProjection } from "@tutti/shared/schemas/api";
|
|
3
|
+
import type { ProcedureProgressUpdate } from "../procedure-engine/index.js";
|
|
3
4
|
import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
|
|
4
5
|
import type { InitializeProjectDocsResult, ProjectDocsInitializationContext, ProjectDocsInitializationDocument } from "../workspace-ops/index.js";
|
|
5
6
|
import type { ControlPlaneLogger, Phase5ControlPlaneOptions } from "./types.js";
|
|
@@ -20,6 +21,7 @@ export declare function tryProjectContextBootstrapDocuments(input: {
|
|
|
20
21
|
workflowRef: WorkflowInvocationRef;
|
|
21
22
|
activityRef: ActivityRef;
|
|
22
23
|
logger?: ControlPlaneLogger | undefined;
|
|
24
|
+
reportProgress?: (progress: ProcedureProgressUpdate) => void;
|
|
23
25
|
now: () => Date;
|
|
24
26
|
}): Promise<ProjectDocsInitializationDocument[] | undefined>;
|
|
25
27
|
export declare function applyProjectDocsInitializationResult(input: {
|
|
@@ -65,6 +65,7 @@ export async function tryProjectContextBootstrapDocuments(input) {
|
|
|
65
65
|
const output = await input.runner.runProjectContextBootstrap(workflowInput, {
|
|
66
66
|
workflow_ref: input.workflowRef,
|
|
67
67
|
activity_ref: input.activityRef,
|
|
68
|
+
...(input.reportProgress === undefined ? {} : { reportProgress: input.reportProgress }),
|
|
68
69
|
});
|
|
69
70
|
input.logger?.info({
|
|
70
71
|
workflow_ref: input.workflowRef,
|
|
@@ -81,6 +81,9 @@ async function runReferenceSummaryRefresh(options) {
|
|
|
81
81
|
}, {
|
|
82
82
|
workflow_ref: options.workflowRef,
|
|
83
83
|
activity_ref: options.activityRef,
|
|
84
|
+
...(options.reportProgress === undefined
|
|
85
|
+
? {}
|
|
86
|
+
: { reportProgress: options.reportProgress }),
|
|
84
87
|
});
|
|
85
88
|
if (output === undefined) {
|
|
86
89
|
adapterUnavailableCount += 1;
|
|
@@ -308,6 +311,7 @@ export function startReferenceSummaryRefreshProcedure(input) {
|
|
|
308
311
|
projectContext,
|
|
309
312
|
events: input.events,
|
|
310
313
|
logger: input.logger,
|
|
314
|
+
reportProgress: context.reportProgress,
|
|
311
315
|
now: input.now,
|
|
312
316
|
}),
|
|
313
317
|
});
|
|
@@ -91,6 +91,7 @@ export function startControlPlaneRunForTask(input) {
|
|
|
91
91
|
on_stage: (progress) => context.reportProgress({
|
|
92
92
|
phase: progress.stage,
|
|
93
93
|
summary: runStageSummary(input.run, progress),
|
|
94
|
+
...(progress.runtime === undefined ? {} : { runtime: progress.runtime }),
|
|
94
95
|
}),
|
|
95
96
|
});
|
|
96
97
|
const resultRecord = recordRunPipelineResult({
|
|
@@ -48,6 +48,7 @@ export function startControlPlaneTaskCompileContinuation(options) {
|
|
|
48
48
|
}, {
|
|
49
49
|
workflow_ref: workflowRef,
|
|
50
50
|
activity_ref: context.activity_ref,
|
|
51
|
+
reportProgress: context.reportProgress,
|
|
51
52
|
});
|
|
52
53
|
const conclusion = applyTaskCompileProcedureOutput({
|
|
53
54
|
store: options.store,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ActivityRef, type WorkflowInvocationRef } from "@tutti/shared/ids";
|
|
2
2
|
import type { SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult } from "@tutti/shared/schemas/api";
|
|
3
|
-
import type { ProcedureEngine, ProcedureEngineState } from "../procedure-engine/index.js";
|
|
3
|
+
import type { ProcedureEngine, ProcedureEngineState, ProcedureProgressUpdate } from "../procedure-engine/index.js";
|
|
4
4
|
import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
|
|
5
5
|
import type { HostProjectStore } from "../store/index.js";
|
|
6
6
|
import type { ControlPlaneCommandResult, ControlPlaneLogger, Phase5ControlPlaneOptions } from "./types.js";
|
|
@@ -25,6 +25,7 @@ export declare function runControlPlaneTaskCompileProcedure(options: {
|
|
|
25
25
|
events: WorkspaceEventBus;
|
|
26
26
|
now: () => Date;
|
|
27
27
|
onTasksAdded: () => void;
|
|
28
|
+
reportProgress?: (progress: ProcedureProgressUpdate) => void;
|
|
28
29
|
}): Promise<{
|
|
29
30
|
kind: "needs_human";
|
|
30
31
|
summary: string;
|
|
@@ -88,6 +88,7 @@ function startControlPlaneTaskCompileActivity(options) {
|
|
|
88
88
|
events: options.events,
|
|
89
89
|
workflowRef,
|
|
90
90
|
activityRef: context.activity_ref,
|
|
91
|
+
reportProgress: context.reportProgress,
|
|
91
92
|
now: options.now,
|
|
92
93
|
onTasksAdded: options.onTasksAdded,
|
|
93
94
|
}),
|
|
@@ -212,6 +213,7 @@ function startProjectContextBootstrapActivity(options) {
|
|
|
212
213
|
workflowRef,
|
|
213
214
|
activityRef: context.activity_ref,
|
|
214
215
|
logger: options.logger,
|
|
216
|
+
reportProgress: context.reportProgress,
|
|
215
217
|
now: options.now,
|
|
216
218
|
});
|
|
217
219
|
const initialized = initializeProjectDocs({
|
|
@@ -266,6 +268,9 @@ export async function runControlPlaneTaskCompileProcedure(options) {
|
|
|
266
268
|
}, {
|
|
267
269
|
workflow_ref: options.workflowRef,
|
|
268
270
|
activity_ref: options.activityRef,
|
|
271
|
+
...(options.reportProgress === undefined
|
|
272
|
+
? {}
|
|
273
|
+
: { reportProgress: options.reportProgress }),
|
|
269
274
|
});
|
|
270
275
|
return applyTaskCompileProcedureOutput({
|
|
271
276
|
store: options.store,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AccountAvatarProjection, RunLineage } from "@tutti/shared/domain";
|
|
2
2
|
import type { RuntimeLogger } from "@tutti/shared/utils";
|
|
3
3
|
import type { ActivityRef, ClarificationRoundRef, TaskId, TaskModuleId } from "@tutti/shared/ids";
|
|
4
|
-
import type { ActiveApprovalProjection, ClarificationRequestPayload, MessageProjection, RecoverySummaryProjection, TaskDetailProjection, TaskProjection } from "@tutti/shared/schemas/api";
|
|
4
|
+
import type { ActiveApprovalProjection, ClarificationRequestPayload, ExecutionRuntimeSnapshot, MessageProjection, RecoverySummaryProjection, TaskDetailProjection, TaskProjection } from "@tutti/shared/schemas/api";
|
|
5
5
|
import type { HostProjectStore } from "../store/index.js";
|
|
6
6
|
import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
|
|
7
7
|
import type { CleanupStaleRunWorkspacesResult } from "../workspace-ops/index.js";
|
|
@@ -102,6 +102,7 @@ export type RunPipelineInput = {
|
|
|
102
102
|
export type RunPipelineStageProgress = {
|
|
103
103
|
stage: string;
|
|
104
104
|
summary: string;
|
|
105
|
+
runtime?: ExecutionRuntimeSnapshot;
|
|
105
106
|
};
|
|
106
107
|
export type RunPipelineTimelineEvent = {
|
|
107
108
|
event_kind: "promotion_reconcile";
|
|
@@ -44,6 +44,21 @@ function readOnlyAppServerOptions(options, prompt, outputSchema, skillSelectionN
|
|
|
44
44
|
: { agentContext: options.readOnlyAppServer.agentContext }),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
function readOnlyRuntimeTelemetry(context, stageId) {
|
|
48
|
+
if (context?.reportProgress === undefined) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
runtimeTelemetry: {
|
|
53
|
+
activityRef: context.activity_ref,
|
|
54
|
+
stageId,
|
|
55
|
+
onSnapshot: (snapshot) => context.reportProgress?.({
|
|
56
|
+
phase: stageId,
|
|
57
|
+
runtime: snapshot,
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
47
62
|
function referenceSummarySourcePath(options, input) {
|
|
48
63
|
if (options.readOnlyAppServer === undefined) {
|
|
49
64
|
throw new Error("Codex app-server read-only Procedure adapter is not configured");
|
|
@@ -266,6 +281,7 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
266
281
|
});
|
|
267
282
|
const result = await runCodexAppServerReadOnlyProcedure({
|
|
268
283
|
...readOnlyAppServerOptions(options, instructions, TASK_COMPILE_OUTPUT_SCHEMA, "task_compile"),
|
|
284
|
+
...readOnlyRuntimeTelemetry(context, "task_compile"),
|
|
269
285
|
validateOutput: isTaskCompileStructuredOutput,
|
|
270
286
|
networkAccess: false,
|
|
271
287
|
webSearchMode: "disabled",
|
|
@@ -314,6 +330,7 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
314
330
|
});
|
|
315
331
|
const result = await runCodexAppServerReadOnlyProcedure({
|
|
316
332
|
...readOnlyAppServerOptions(options, instructions, PROJECT_CONTEXT_BOOTSTRAP_OUTPUT_SCHEMA, "project_context_bootstrap"),
|
|
333
|
+
...readOnlyRuntimeTelemetry(context, "project_context_bootstrap"),
|
|
317
334
|
validateOutput: isProjectContextBootstrapWorkflowOutput,
|
|
318
335
|
});
|
|
319
336
|
recordUsage(options, "project_context_bootstrap", result, {
|
|
@@ -329,6 +346,7 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
329
346
|
});
|
|
330
347
|
const result = await runCodexAppServerReadOnlyProcedure({
|
|
331
348
|
...readOnlyAppServerOptions(options, instructions, CONTEXT_SYNC_OUTPUT_SCHEMA, "context_sync"),
|
|
349
|
+
...readOnlyRuntimeTelemetry(context, "context_sync"),
|
|
332
350
|
validateOutput: isContextSyncWorkflowOutput,
|
|
333
351
|
});
|
|
334
352
|
recordUsage(options, "context_sync", result, {
|
|
@@ -350,6 +368,7 @@ export function createOpenAiProcedureWorkflowRunner(options) {
|
|
|
350
368
|
try {
|
|
351
369
|
const result = await runCodexAppServerReadOnlyProcedure({
|
|
352
370
|
...readOnlyAppServerOptions(options, instructions, REFERENCE_FILE_SUMMARY_OUTPUT_SCHEMA, "reference_summary_refresh"),
|
|
371
|
+
...readOnlyRuntimeTelemetry(context, "reference_summary_refresh"),
|
|
353
372
|
appServerCwd: workspace.cwd,
|
|
354
373
|
validateOutput: isReferenceFileSummaryStructuredOutput,
|
|
355
374
|
readablePaths: [workspace.cwd],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageProjection, RunResultProjection, ScratchpadProjection, TaskDetailProjection, WorklistProjection } from "@tutti/shared/schemas/api";
|
|
1
|
+
import type { ExecutionRuntimeSnapshot, MessageProjection, RunResultProjection, ScratchpadProjection, TaskDetailProjection, WorklistProjection } from "@tutti/shared/schemas/api";
|
|
2
2
|
import type { ClarificationRequestPayload, TaskContractProjection } from "@tutti/shared/schemas/api";
|
|
3
3
|
import type { ProjectDocKey } from "@tutti/shared/schemas/api";
|
|
4
4
|
import type { ActivityRef, ClarificationRoundRef, ClarificationThreadRef, MessageId, TaskId, TaskModuleId, WorkflowInvocationRef } from "@tutti/shared/ids";
|
|
@@ -29,6 +29,11 @@ export type ScratchpadRefreshWorkflowContext = {
|
|
|
29
29
|
export type ProcedureWorkflowRunContext = {
|
|
30
30
|
workflow_ref: WorkflowInvocationRef;
|
|
31
31
|
activity_ref: ActivityRef;
|
|
32
|
+
reportProgress?: (progress: {
|
|
33
|
+
summary?: string;
|
|
34
|
+
phase?: string;
|
|
35
|
+
runtime?: ExecutionRuntimeSnapshot;
|
|
36
|
+
}) => void;
|
|
32
37
|
};
|
|
33
38
|
export type ScratchpadRefreshWorkflowOutput = {
|
|
34
39
|
decision: "ready";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ActivityRef, type WorkflowInvocationRef } from "@tutti/shared/ids";
|
|
2
2
|
import type { ResultAnchor, WorkflowKind } from "@tutti/shared/domain";
|
|
3
|
-
import type { ActivityProjection, ExecutionStatusProjection, ProcedureLane } from "@tutti/shared/schemas/api";
|
|
3
|
+
import type { ActivityProjection, ExecutionRuntimeSnapshot, ExecutionStatusProjection, ProcedureLane } from "@tutti/shared/schemas/api";
|
|
4
4
|
export type ProcedureTerminalConclusion = {
|
|
5
5
|
kind: "completed" | "failed" | "needs_human";
|
|
6
6
|
summary: string;
|
|
@@ -11,6 +11,12 @@ export type ProcedureExecutionContext = {
|
|
|
11
11
|
workflow_ref: WorkflowInvocationRef;
|
|
12
12
|
workflow_kind: WorkflowKind;
|
|
13
13
|
lane: ProcedureLane;
|
|
14
|
+
reportProgress: (progress: ProcedureProgressUpdate) => void;
|
|
15
|
+
};
|
|
16
|
+
export type ProcedureProgressUpdate = {
|
|
17
|
+
summary?: string;
|
|
18
|
+
phase?: string;
|
|
19
|
+
runtime?: ExecutionRuntimeSnapshot;
|
|
14
20
|
};
|
|
15
21
|
export type StartProcedureOptions = {
|
|
16
22
|
workflow_ref: WorkflowInvocationRef;
|
|
@@ -36,6 +42,8 @@ export type StartProcedureResult = {
|
|
|
36
42
|
export type ProcedureEngineState = {
|
|
37
43
|
activity: ActivityProjection;
|
|
38
44
|
execution_status: ExecutionStatusProjection;
|
|
45
|
+
transition_kind: "started" | "progress" | "terminal";
|
|
46
|
+
progress?: ProcedureProgressUpdate;
|
|
39
47
|
};
|
|
40
48
|
export declare class ProcedureEngine {
|
|
41
49
|
private readonly activeByLane;
|
|
@@ -49,6 +57,7 @@ export declare class ProcedureEngine {
|
|
|
49
57
|
getExecutionStatus(): ExecutionStatusProjection;
|
|
50
58
|
waitForIdle(): Promise<void>;
|
|
51
59
|
private stateForActivity;
|
|
60
|
+
private updateActiveProgress;
|
|
52
61
|
private finishActive;
|
|
53
62
|
private primaryActiveProcedure;
|
|
54
63
|
private orderedActiveProcedures;
|
|
@@ -39,6 +39,7 @@ export class ProcedureEngine {
|
|
|
39
39
|
workflow_ref: options.workflow_ref,
|
|
40
40
|
workflow_kind: options.workflow_kind,
|
|
41
41
|
lane,
|
|
42
|
+
reportProgress: (progress) => this.updateActiveProgress(activityRef, lane, progress, options.onTransition),
|
|
42
43
|
};
|
|
43
44
|
const run = async () => {
|
|
44
45
|
try {
|
|
@@ -60,7 +61,7 @@ export class ProcedureEngine {
|
|
|
60
61
|
};
|
|
61
62
|
const promise = Promise.resolve().then(run);
|
|
62
63
|
this.activeByLane.set(lane, { activity, lane, promise });
|
|
63
|
-
options.onTransition?.(this.stateForActivity(activity));
|
|
64
|
+
options.onTransition?.(this.stateForActivity(activity, "started"));
|
|
64
65
|
return {
|
|
65
66
|
kind: "accepted",
|
|
66
67
|
activity,
|
|
@@ -73,13 +74,17 @@ export class ProcedureEngine {
|
|
|
73
74
|
const active = this.primaryActiveProcedure();
|
|
74
75
|
if (active !== null) {
|
|
75
76
|
const activeActivities = this.orderedActiveProcedures().map((entry) => executionActivityFromProcedure(entry.activity));
|
|
76
|
-
|
|
77
|
+
const status = {
|
|
77
78
|
status: "running",
|
|
78
79
|
summary: active.activity.summary,
|
|
79
80
|
active_activity: executionActivityFromProcedure(active.activity),
|
|
80
81
|
active_activities: activeActivities,
|
|
81
82
|
updated_at: this.now().toISOString(),
|
|
82
83
|
};
|
|
84
|
+
if (active.runtime !== undefined) {
|
|
85
|
+
status.runtime = active.runtime;
|
|
86
|
+
}
|
|
87
|
+
return status;
|
|
83
88
|
}
|
|
84
89
|
const idle = {
|
|
85
90
|
status: "idle",
|
|
@@ -96,11 +101,35 @@ export class ProcedureEngine {
|
|
|
96
101
|
await Promise.all(this.orderedActiveProcedures().map((active) => active.promise));
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
|
-
stateForActivity(activity) {
|
|
100
|
-
|
|
104
|
+
stateForActivity(activity, transitionKind, progress) {
|
|
105
|
+
const state = {
|
|
101
106
|
activity,
|
|
102
107
|
execution_status: this.getExecutionStatus(),
|
|
108
|
+
transition_kind: transitionKind,
|
|
103
109
|
};
|
|
110
|
+
if (progress !== undefined) {
|
|
111
|
+
state.progress = progress;
|
|
112
|
+
}
|
|
113
|
+
return state;
|
|
114
|
+
}
|
|
115
|
+
updateActiveProgress(activityRef, lane, progress, onTransition) {
|
|
116
|
+
const active = this.activeByLane.get(lane);
|
|
117
|
+
if (active === undefined || active.activity.activity_ref !== activityRef) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (progress.summary !== undefined) {
|
|
121
|
+
active.activity.summary = progress.summary;
|
|
122
|
+
}
|
|
123
|
+
if (progress.runtime !== undefined) {
|
|
124
|
+
active.runtime = progress.runtime;
|
|
125
|
+
}
|
|
126
|
+
else if (progress.phase !== undefined && active.runtime !== undefined) {
|
|
127
|
+
active.runtime = {
|
|
128
|
+
...active.runtime,
|
|
129
|
+
stage_id: progress.phase,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
onTransition?.(this.stateForActivity(active.activity, "progress", progress));
|
|
104
133
|
}
|
|
105
134
|
finishActive(activityRef, lane, conclusion, onTransition) {
|
|
106
135
|
const active = this.activeByLane.get(lane);
|
|
@@ -132,6 +161,7 @@ export class ProcedureEngine {
|
|
|
132
161
|
onTransition?.({
|
|
133
162
|
activity: active.activity,
|
|
134
163
|
execution_status: this.getExecutionStatus(),
|
|
164
|
+
transition_kind: "terminal",
|
|
135
165
|
});
|
|
136
166
|
}
|
|
137
167
|
primaryActiveProcedure() {
|
|
@@ -23,12 +23,17 @@ export type CodexAppServerJsonRpcError = {
|
|
|
23
23
|
message: string;
|
|
24
24
|
data?: unknown;
|
|
25
25
|
};
|
|
26
|
+
export type CodexAppServerJsonRpcClientOptions = {
|
|
27
|
+
onNotification?: (notification: CodexAppServerJsonRpcNotification) => void;
|
|
28
|
+
onServerRequest?: (request: CodexAppServerJsonRpcServerRequest) => void;
|
|
29
|
+
};
|
|
26
30
|
export declare class CodexAppServerProtocolError extends Error {
|
|
27
31
|
readonly code: string;
|
|
28
32
|
constructor(message: string, code: string);
|
|
29
33
|
}
|
|
30
34
|
export declare class CodexAppServerJsonRpcClient {
|
|
31
35
|
private readonly child;
|
|
36
|
+
private readonly options;
|
|
32
37
|
private readonly decoder;
|
|
33
38
|
private readonly pendingRequests;
|
|
34
39
|
private readonly notificationWaiters;
|
|
@@ -42,7 +47,7 @@ export declare class CodexAppServerJsonRpcClient {
|
|
|
42
47
|
private readonly notificationList;
|
|
43
48
|
private readonly serverRequestList;
|
|
44
49
|
private readonly unresolvedServerRequestIds;
|
|
45
|
-
constructor(child: ChildProcessWithoutNullStreams);
|
|
50
|
+
constructor(child: ChildProcessWithoutNullStreams, options?: CodexAppServerJsonRpcClientOptions);
|
|
46
51
|
get notifications(): readonly CodexAppServerJsonRpcNotification[];
|
|
47
52
|
get serverRequests(): readonly CodexAppServerJsonRpcServerRequest[];
|
|
48
53
|
get stderr(): string;
|
|
@@ -23,6 +23,7 @@ function normalizeProtocolError(method, error) {
|
|
|
23
23
|
}
|
|
24
24
|
export class CodexAppServerJsonRpcClient {
|
|
25
25
|
child;
|
|
26
|
+
options;
|
|
26
27
|
decoder = new StringDecoder("utf8");
|
|
27
28
|
pendingRequests = new Map();
|
|
28
29
|
notificationWaiters = new Set();
|
|
@@ -36,8 +37,9 @@ export class CodexAppServerJsonRpcClient {
|
|
|
36
37
|
notificationList = [];
|
|
37
38
|
serverRequestList = [];
|
|
38
39
|
unresolvedServerRequestIds = new Set();
|
|
39
|
-
constructor(child) {
|
|
40
|
+
constructor(child, options = {}) {
|
|
40
41
|
this.child = child;
|
|
42
|
+
this.options = options;
|
|
41
43
|
child.stdout.on("data", (chunk) => this.handleStdout(chunk));
|
|
42
44
|
child.stderr.on("data", (chunk) => {
|
|
43
45
|
this.recordActivity();
|
|
@@ -272,6 +274,12 @@ export class CodexAppServerJsonRpcClient {
|
|
|
272
274
|
recordNotification(notification) {
|
|
273
275
|
this.recordActivity();
|
|
274
276
|
this.notificationList.push(notification);
|
|
277
|
+
try {
|
|
278
|
+
this.options.onNotification?.(notification);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Runtime observers must not affect JSON-RPC delivery.
|
|
282
|
+
}
|
|
275
283
|
for (const waiter of Array.from(this.notificationWaiters)) {
|
|
276
284
|
if (!waiter.predicate(notification)) {
|
|
277
285
|
continue;
|
|
@@ -285,6 +293,12 @@ export class CodexAppServerJsonRpcClient {
|
|
|
285
293
|
this.recordActivity();
|
|
286
294
|
this.serverRequestList.push(request);
|
|
287
295
|
this.unresolvedServerRequestIds.add(request.id);
|
|
296
|
+
try {
|
|
297
|
+
this.options.onServerRequest?.(request);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Runtime observers must not affect interactive request handling.
|
|
301
|
+
}
|
|
288
302
|
for (const waiter of Array.from(this.serverRequestWaiters)) {
|
|
289
303
|
clearTimeout(waiter.timer);
|
|
290
304
|
this.serverRequestWaiters.delete(waiter);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CodexAppServerRuntimeTelemetryOptions } from "./runtime-telemetry.js";
|
|
1
2
|
import { type CodexAppServerAgentContextRuntime, type CodexAppServerSkillSelectionName } from "./skills.js";
|
|
2
3
|
import { type OpenAiTokenUsage } from "../token-usage.js";
|
|
3
4
|
export type CodexAppServerWebSearchMode = "disabled" | "cached" | "live";
|
|
@@ -22,6 +23,7 @@ export type CodexAppServerReadOnlyProcedureInput<TOutput> = {
|
|
|
22
23
|
requestTimeoutMs?: number;
|
|
23
24
|
turnTimeoutMs?: number;
|
|
24
25
|
baseEnv?: NodeJS.ProcessEnv;
|
|
26
|
+
runtimeTelemetry?: CodexAppServerRuntimeTelemetryOptions;
|
|
25
27
|
};
|
|
26
28
|
export type CodexAppServerReadOnlyProcedureResult<TOutput> = {
|
|
27
29
|
output: TOutput;
|
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { buildCodexAppServerProcessPlan } from "../codex-app-server.js";
|
|
6
6
|
import { DEFAULT_OPENAI_MODEL } from "../model-config.js";
|
|
7
7
|
import { CodexAppServerJsonRpcClient, CodexAppServerProtocolError, } from "./json-rpc.js";
|
|
8
|
+
import { createCodexAppServerRuntimeObserver, } from "./runtime-telemetry.js";
|
|
8
9
|
import { createReadOnlySandboxPolicy } from "./sandbox-policy.js";
|
|
9
10
|
import { buildCodexAppServerProviderConfigOverrides, resolveCodexAppServerModelProvider, } from "./provider-config.js";
|
|
10
11
|
import { buildCodexAppServerTurnInput, resolveCodexAppServerContextRuntimeHint, resolveCodexAppServerSkillInputs, CodexAppServerSkillSelectionError, } from "./skills.js";
|
|
@@ -125,7 +126,17 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
|
|
|
125
126
|
env: createCodexSpawnEnv(codexHome, options.baseEnv ?? process.env),
|
|
126
127
|
stdio: "pipe",
|
|
127
128
|
});
|
|
128
|
-
const
|
|
129
|
+
const runtimeObserver = options.runtimeTelemetry === undefined
|
|
130
|
+
? undefined
|
|
131
|
+
: createCodexAppServerRuntimeObserver(options.runtimeTelemetry);
|
|
132
|
+
const client = new CodexAppServerJsonRpcClient(child, {
|
|
133
|
+
...(runtimeObserver === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: {
|
|
136
|
+
onNotification: runtimeObserver.notification,
|
|
137
|
+
onServerRequest: runtimeObserver.serverRequest,
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
129
140
|
const sensitiveValues = [
|
|
130
141
|
options.apiKey,
|
|
131
142
|
tempRoot,
|
|
@@ -284,6 +295,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
|
|
|
284
295
|
throw new CodexAppServerReadOnlyProcedureError("app_server_protocol_error", redactWithKnownValues(message, sensitiveValues));
|
|
285
296
|
}
|
|
286
297
|
finally {
|
|
298
|
+
runtimeObserver?.flush();
|
|
287
299
|
client.dispose();
|
|
288
300
|
rmSync(tempRoot, { recursive: true, force: true });
|
|
289
301
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ActivityRef } from "@tutti/shared/ids";
|
|
2
|
+
import type { ExecutionRuntimeSnapshot } from "@tutti/shared/schemas/api";
|
|
3
|
+
import type { CodexAppServerJsonRpcNotification, CodexAppServerJsonRpcServerRequest } from "./json-rpc.js";
|
|
4
|
+
export type CodexAppServerRuntimeTelemetryOptions = {
|
|
5
|
+
activityRef: ActivityRef;
|
|
6
|
+
stageId?: string;
|
|
7
|
+
startedAt?: string;
|
|
8
|
+
throttleMs?: number;
|
|
9
|
+
now?: () => Date;
|
|
10
|
+
onSnapshot?: (snapshot: ExecutionRuntimeSnapshot) => void;
|
|
11
|
+
};
|
|
12
|
+
export type CodexAppServerRuntimeObserver = {
|
|
13
|
+
notification: (notification: CodexAppServerJsonRpcNotification) => void;
|
|
14
|
+
serverRequest: (request: CodexAppServerJsonRpcServerRequest) => void;
|
|
15
|
+
flush: () => void;
|
|
16
|
+
};
|
|
17
|
+
export declare function createCodexAppServerRuntimeObserver(options: CodexAppServerRuntimeTelemetryOptions): CodexAppServerRuntimeObserver;
|
|
18
|
+
//# sourceMappingURL=runtime-telemetry.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { extractCodexAppServerTokenUsage } from "../token-usage.js";
|
|
2
|
+
const DEFAULT_THROTTLE_MS = 5_000;
|
|
3
|
+
function searchText(value) {
|
|
4
|
+
if (typeof value === "string") {
|
|
5
|
+
return value.toLocaleLowerCase();
|
|
6
|
+
}
|
|
7
|
+
if (typeof value !== "object" || value === null) {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
const record = value;
|
|
11
|
+
const tokens = ["type", "kind", "name", "status", "method", "state"]
|
|
12
|
+
.map((key) => record[key])
|
|
13
|
+
.filter((entry) => typeof entry === "string");
|
|
14
|
+
return tokens.join(" ").toLocaleLowerCase();
|
|
15
|
+
}
|
|
16
|
+
function actionFromNotification(notification) {
|
|
17
|
+
const method = notification.method;
|
|
18
|
+
const paramsText = searchText(notification.params);
|
|
19
|
+
const combined = `${method} ${paramsText}`.toLocaleLowerCase();
|
|
20
|
+
if (combined.includes("commandexecution") || combined.includes("command_execution")) {
|
|
21
|
+
return "running_command";
|
|
22
|
+
}
|
|
23
|
+
if (combined.includes("filechange") || combined.includes("file_change")) {
|
|
24
|
+
return "editing_files";
|
|
25
|
+
}
|
|
26
|
+
if (combined.includes("plan")) {
|
|
27
|
+
return "planning";
|
|
28
|
+
}
|
|
29
|
+
if (combined.includes("mcptoolcall") ||
|
|
30
|
+
combined.includes("mcp_tool_call") ||
|
|
31
|
+
combined.includes("dynamictoolcall") ||
|
|
32
|
+
combined.includes("dynamic_tool_call") ||
|
|
33
|
+
combined.includes("websearch") ||
|
|
34
|
+
combined.includes("web_search")) {
|
|
35
|
+
return "using_tool";
|
|
36
|
+
}
|
|
37
|
+
if (combined.includes("reasoning")) {
|
|
38
|
+
return "reading_context";
|
|
39
|
+
}
|
|
40
|
+
if (method === "item/agentMessage/delta" || method === "item/completed") {
|
|
41
|
+
return "finalizing";
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
function runtimeTokenUsage(usage) {
|
|
46
|
+
return {
|
|
47
|
+
input_tokens: usage.input_tokens,
|
|
48
|
+
cached_input_tokens: usage.cached_input_tokens,
|
|
49
|
+
output_tokens: usage.output_tokens,
|
|
50
|
+
reasoning_output_tokens: usage.reasoning_output_tokens,
|
|
51
|
+
total_tokens: usage.total_tokens,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function createCodexAppServerRuntimeObserver(options) {
|
|
55
|
+
let latest;
|
|
56
|
+
let timer;
|
|
57
|
+
let lastEmittedAt = 0;
|
|
58
|
+
const throttleMs = options.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
59
|
+
const now = options.now ?? (() => new Date());
|
|
60
|
+
const emit = () => {
|
|
61
|
+
if (latest === undefined || options.onSnapshot === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
lastEmittedAt = Date.now();
|
|
65
|
+
options.onSnapshot(latest);
|
|
66
|
+
};
|
|
67
|
+
const scheduleEmit = () => {
|
|
68
|
+
if (options.onSnapshot === undefined) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const elapsed = Date.now() - lastEmittedAt;
|
|
72
|
+
if (elapsed >= throttleMs) {
|
|
73
|
+
if (timer !== undefined) {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
timer = undefined;
|
|
76
|
+
}
|
|
77
|
+
emit();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (timer !== undefined) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
timer = setTimeout(() => {
|
|
84
|
+
timer = undefined;
|
|
85
|
+
emit();
|
|
86
|
+
}, Math.max(1, throttleMs - elapsed));
|
|
87
|
+
};
|
|
88
|
+
const update = (patch) => {
|
|
89
|
+
if (patch.currentAction === undefined && patch.tokenUsage === undefined) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const updatedAt = now().toISOString();
|
|
93
|
+
latest = {
|
|
94
|
+
activity_ref: options.activityRef,
|
|
95
|
+
...(options.stageId === undefined ? {} : { stage_id: options.stageId }),
|
|
96
|
+
...(options.startedAt === undefined ? {} : { started_at: options.startedAt }),
|
|
97
|
+
...(latest?.token_usage === undefined ? {} : { token_usage: latest.token_usage }),
|
|
98
|
+
...(latest?.current_action === undefined ? {} : { current_action: latest.current_action }),
|
|
99
|
+
updated_at: updatedAt,
|
|
100
|
+
};
|
|
101
|
+
if (patch.tokenUsage !== undefined) {
|
|
102
|
+
latest.token_usage = runtimeTokenUsage(patch.tokenUsage);
|
|
103
|
+
}
|
|
104
|
+
if (patch.currentAction !== undefined) {
|
|
105
|
+
latest.current_action = patch.currentAction;
|
|
106
|
+
}
|
|
107
|
+
scheduleEmit();
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
notification: (notification) => {
|
|
111
|
+
const tokenUsage = extractCodexAppServerTokenUsage([notification]);
|
|
112
|
+
const currentAction = actionFromNotification(notification);
|
|
113
|
+
update({
|
|
114
|
+
...(tokenUsage === undefined ? {} : { tokenUsage }),
|
|
115
|
+
...(currentAction === undefined ? {} : { currentAction }),
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
serverRequest: () => {
|
|
119
|
+
update({ currentAction: "waiting_for_approval" });
|
|
120
|
+
},
|
|
121
|
+
flush: () => {
|
|
122
|
+
if (timer !== undefined) {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
timer = undefined;
|
|
125
|
+
}
|
|
126
|
+
emit();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=runtime-telemetry.js.map
|