@xfey/tutti 0.1.78 → 0.1.80
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 +1 -1
- package/dist/chat-assistant/index.d.ts +11 -1
- package/dist/chat-assistant/index.js +61 -7
- package/dist/checks/index.d.ts +22 -0
- package/dist/checks/index.js +110 -4
- package/dist/control-plane/follow-up-start.d.ts +1 -0
- package/dist/control-plane/follow-up-start.js +3 -1
- package/dist/control-plane/index.d.ts +6 -1
- package/dist/control-plane/index.js +72 -11
- package/dist/control-plane/procedure-logging.js +24 -67
- package/dist/control-plane/project-context-bootstrap.js +7 -1
- package/dist/control-plane/reference-summary-refresh.js +1 -1
- package/dist/control-plane/run-result-recording.js +2 -1
- package/dist/control-plane/task-compile-start.d.ts +1 -0
- package/dist/control-plane/task-compile-start.js +11 -1
- package/dist/control-plane/workflows/openai.d.ts +6 -0
- package/dist/control-plane/workflows/openai.js +16 -3
- package/dist/procedure-engine/index.d.ts +1 -0
- package/dist/procedure-engine/index.js +7 -1
- package/dist/process-tree.d.ts +25 -0
- package/dist/process-tree.js +121 -0
- package/dist/providers/openai/app-server/invocation-coordinator.d.ts +28 -0
- package/dist/providers/openai/app-server/invocation-coordinator.js +104 -0
- package/dist/providers/openai/app-server/invocation-lifecycle.d.ts +45 -0
- package/dist/providers/openai/app-server/invocation-lifecycle.js +104 -0
- package/dist/providers/openai/app-server/json-rpc.d.ts +1 -1
- package/dist/providers/openai/app-server/json-rpc.js +9 -16
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +8 -2
- package/dist/providers/openai/app-server/read-only-procedure.js +63 -11
- package/dist/providers/openai/app-server/smoke-types.d.ts +4 -2
- package/dist/providers/openai/app-server/smoke.js +41 -12
- package/dist/providers/openai/app-server/stale-temp-cleanup.d.ts +17 -0
- package/dist/providers/openai/app-server/stale-temp-cleanup.js +130 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +8 -2
- package/dist/providers/openai/app-server/workspace-write-run.js +63 -11
- package/dist/providers/openai/chat-assistant.d.ts +6 -0
- package/dist/providers/openai/chat-assistant.js +12 -0
- package/dist/providers/openai/index.d.ts +1 -0
- package/dist/providers/openai/index.js +1 -0
- package/dist/providers/openai/machine-local-files.d.ts +1 -1
- package/dist/providers/openai/machine-local-files.js +32 -9
- package/dist/providers/openai/provider-config.d.ts +5 -1
- package/dist/providers/openai/provider-config.js +25 -4
- package/dist/providers/openai/provider-models.d.ts +41 -0
- package/dist/providers/openai/provider-models.js +82 -0
- package/dist/providers/openai/provider-setup.js +5 -2
- package/dist/run-engine/index.js +2 -1
- package/dist/run-pipeline/artifact-applicability.js +2 -2
- package/dist/run-pipeline/openai.d.ts +9 -0
- package/dist/run-pipeline/openai.js +26 -26
- package/dist/run-pipeline/task-run-invocation.js +6 -0
- package/dist/runtime-failures.d.ts +3 -0
- package/dist/runtime-failures.js +22 -0
- package/dist/server-shell/cli/host-lifecycle.js +1 -1
- package/dist/server-shell/cli/host-project-runtime-lifecycle.d.ts +43 -0
- package/dist/server-shell/cli/host-project-runtime-lifecycle.js +54 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +1 -1
- package/dist/server-shell/cli/host-server-runtime.js +140 -15
- package/dist/server-shell/http/create-server.d.ts +3 -0
- package/dist/server-shell/http/create-server.js +10 -4
- package/dist/server-shell/http/routes/project-api/invalidations.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/invalidations.js +4 -5
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +38 -14
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.js +26 -1
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +38 -14
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +8 -2
- package/dist/server-shell/http/routes/project-api/schemas.js +2 -0
- package/dist/server-shell/http/routes/project-api/session-requirements.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/session-requirements.js +7 -0
- package/dist/server-shell/http/routes/project-api/system-routes.js +96 -5
- package/dist/server-shell/http/routes/project-api/types.d.ts +10 -1
- package/dist/server-shell/http/routes/project-api/workspace-projections.js +4 -2
- package/dist/server-shell/http/routes/project-api.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +36 -0
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +58 -1
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +9 -1
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-sNI8fBH3.js → homepage-motion-scene-DXzmjALa.js} +1 -1
- package/web/assets/index-9gApbtPC.js +69 -0
- package/web/assets/index-BKeSEhsn.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-BUfAlA_H.css +0 -1
- package/web/assets/index-d-q9yZ09.js +0 -69
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
## 当前状态
|
|
30
30
|
|
|
31
|
-
- 已具备 Fastify host shell、后台 host manager CLI 套件、用户级 desktop integration / strict `tutti://projects` bridge、带 `service status / stop / restart`、卸载前 `shutdown`、并发启动锁、版本替换、strict terminal / desktop browser context、当前 runtime version bootstrap projection、idle / package update cleanup、全机readiness、exact npm global install、跨版本worker / recovery与私有日志的机器级 Local Console、Dev Runner、Host Project API、SSE、SQLite store、collaboration-state、workspace read-state、project timeline observability helper、Control Plane、Procedure / Run、workspace-ops、checks、Relay staged upload finalize、user skills 文件服务、artifacts
|
|
31
|
+
- 已具备 Fastify host shell、后台 host manager CLI 套件、用户级 desktop integration / strict `tutti://projects` bridge、带 `service status / stop / restart`、卸载前 `shutdown`、并发启动锁、版本替换、strict terminal / desktop browser context、当前 runtime version bootstrap projection、idle / package update cleanup、全机readiness、exact npm global install、跨版本worker / recovery与私有日志的机器级 Local Console、Dev Runner、Host Project API、SSE、SQLite store、collaboration-state、workspace read-state、project timeline observability helper、Control Plane、Procedure / Run、workspace-ops、checks、Relay staged upload finalize、user skills 文件服务、artifacts 可视化产物查看层、Owner-only provider model discovery / update 和 OpenAI / Codex provider 接入。
|
|
32
32
|
- 新 Host project 的空群聊会由 collaboration-state 幂等写入一条 Tutti 欢迎消息;它不经过 browser message command,不触发 `@tutti`、Chat read-state、Scratchpad 或 Timeline / execution 流程。
|
|
33
33
|
- Host server 与 Local Console 支持在显式 `webStatic.root` 或 packaged `web/` 存在时服务 Web 静态产物;`/api/*`、SSE、health、host-local control 和 `/local-console/*` API 不被 SPA fallback 吞掉。
|
|
34
34
|
- Root `pack:dry-run` 会先清理 build output、重新构建,再复制 `apps/web/dist` 到 package-local `web/`,生成临时 staging `@xfey/tutti` artifact,并校验 npm pack 文件列表包含 CLI、migrations、prompts、Web index、bundled `@tutti/shared` / `@tutti/relay-client` 以及它们的外部 runtime dependencies,同时排除源码、source map、日志、SQLite、`.env`、`.tutti-dev` 与测试产物。
|
|
@@ -32,6 +32,7 @@ export type ReadOnlyChatAssistantEnqueueResult = {
|
|
|
32
32
|
export type ReadOnlyChatAssistantHandle = {
|
|
33
33
|
enqueue: (input: ReadOnlyChatAssistantTriggerInput) => ReadOnlyChatAssistantEnqueueResult;
|
|
34
34
|
waitForIdle: () => Promise<void>;
|
|
35
|
+
stopAccepting: () => void;
|
|
35
36
|
};
|
|
36
37
|
export type ReadOnlyChatAssistantOptions = {
|
|
37
38
|
project: {
|
|
@@ -44,16 +45,25 @@ export type ReadOnlyChatAssistantOptions = {
|
|
|
44
45
|
model: ReadOnlyChatAssistantModel;
|
|
45
46
|
promptsRoot?: string;
|
|
46
47
|
onScratchpadSourceChanged?: (message: MessageProjection) => void;
|
|
48
|
+
waitForInspectionAvailability?: () => Promise<void>;
|
|
49
|
+
maxQueuedMentions?: number;
|
|
47
50
|
now?: () => Date;
|
|
48
51
|
};
|
|
49
52
|
export declare function isChatAssistantOutput(value: unknown): value is ChatAssistantOutput;
|
|
50
53
|
export declare function containsTuttiMention(value: string): boolean;
|
|
51
54
|
export declare class ReadOnlyChatAssistant implements ReadOnlyChatAssistantHandle {
|
|
52
55
|
private readonly options;
|
|
53
|
-
private readonly
|
|
56
|
+
private readonly pending;
|
|
57
|
+
private readonly idleWaiters;
|
|
58
|
+
private active;
|
|
59
|
+
private accepting;
|
|
60
|
+
private readonly maxQueuedMentions;
|
|
54
61
|
constructor(options: ReadOnlyChatAssistantOptions);
|
|
55
62
|
enqueue(input: ReadOnlyChatAssistantTriggerInput): ReadOnlyChatAssistantEnqueueResult;
|
|
56
63
|
waitForIdle(): Promise<void>;
|
|
64
|
+
stopAccepting(): void;
|
|
65
|
+
private pump;
|
|
66
|
+
private resolveIdleIfNeeded;
|
|
57
67
|
private buildMainChatContext;
|
|
58
68
|
private buildClarificationRoundContext;
|
|
59
69
|
private buildContext;
|
|
@@ -97,23 +97,76 @@ function roundMessagesToContext(messages) {
|
|
|
97
97
|
}
|
|
98
98
|
export class ReadOnlyChatAssistant {
|
|
99
99
|
options;
|
|
100
|
-
|
|
100
|
+
pending = [];
|
|
101
|
+
idleWaiters = new Set();
|
|
102
|
+
active = null;
|
|
103
|
+
accepting = true;
|
|
104
|
+
maxQueuedMentions;
|
|
101
105
|
constructor(options) {
|
|
102
106
|
this.options = options;
|
|
107
|
+
this.maxQueuedMentions = options.maxQueuedMentions ?? 8;
|
|
103
108
|
}
|
|
104
109
|
enqueue(input) {
|
|
105
110
|
if (!containsTuttiMention(input.message.body)) {
|
|
106
111
|
return { triggered: false };
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
if (!this.accepting) {
|
|
114
|
+
return { triggered: false };
|
|
115
|
+
}
|
|
116
|
+
if (this.pending.length >= this.maxQueuedMentions) {
|
|
117
|
+
this.appendAssistantMessageForTrigger(input.message, assistantFailureMessage(), {
|
|
118
|
+
scratchpad_source: "exclude",
|
|
119
|
+
});
|
|
120
|
+
return { triggered: true };
|
|
121
|
+
}
|
|
122
|
+
this.pending.push(input.message);
|
|
123
|
+
this.pump();
|
|
113
124
|
return { triggered: true };
|
|
114
125
|
}
|
|
115
126
|
async waitForIdle() {
|
|
116
|
-
|
|
127
|
+
if (this.active === null && this.pending.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await new Promise((resolve) => this.idleWaiters.add(resolve));
|
|
131
|
+
}
|
|
132
|
+
stopAccepting() {
|
|
133
|
+
if (!this.accepting) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.accepting = false;
|
|
137
|
+
for (const message of this.pending.splice(0)) {
|
|
138
|
+
this.appendAssistantMessageForTrigger(message, assistantFailureMessage(), {
|
|
139
|
+
scratchpad_source: "exclude",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
this.resolveIdleIfNeeded();
|
|
143
|
+
}
|
|
144
|
+
pump() {
|
|
145
|
+
if (this.active !== null) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const message = this.pending.shift();
|
|
149
|
+
if (message === undefined) {
|
|
150
|
+
this.resolveIdleIfNeeded();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const task = this.respond(message).catch(() => undefined);
|
|
154
|
+
this.active = task;
|
|
155
|
+
void task.finally(() => {
|
|
156
|
+
if (this.active === task) {
|
|
157
|
+
this.active = null;
|
|
158
|
+
this.pump();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
resolveIdleIfNeeded() {
|
|
163
|
+
if (this.active !== null || this.pending.length !== 0) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const resolve of this.idleWaiters) {
|
|
167
|
+
resolve();
|
|
168
|
+
}
|
|
169
|
+
this.idleWaiters.clear();
|
|
117
170
|
}
|
|
118
171
|
buildMainChatContext(triggerMessage) {
|
|
119
172
|
const authorDisplayName = triggerMessage.author.display_name?.trim();
|
|
@@ -249,6 +302,7 @@ export class ReadOnlyChatAssistant {
|
|
|
249
302
|
}
|
|
250
303
|
async respond(triggerMessage) {
|
|
251
304
|
try {
|
|
305
|
+
await this.options.waitForInspectionAvailability?.();
|
|
252
306
|
const prompt = this.renderPrompt(triggerMessage);
|
|
253
307
|
if (prompt === null) {
|
|
254
308
|
return;
|
package/dist/checks/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CheckSkipReasonCode } from "@tutti/shared/schemas/api";
|
|
2
|
+
import { type ProcessTreeTerminationOptions } from "../process-tree.js";
|
|
2
3
|
export type CheckCommand = {
|
|
3
4
|
id: string;
|
|
4
5
|
label: string;
|
|
@@ -41,7 +42,28 @@ export type ExecuteCheckPlanOptions = {
|
|
|
41
42
|
workspaceRoot: string;
|
|
42
43
|
plan: CheckPlan;
|
|
43
44
|
timeoutMs?: number;
|
|
45
|
+
termination?: ProcessTreeTerminationOptions;
|
|
46
|
+
processCoordinator?: CheckProcessCoordinator;
|
|
44
47
|
};
|
|
48
|
+
type ActiveCheckProcess = {
|
|
49
|
+
markAborted: () => void;
|
|
50
|
+
terminate: (options?: ProcessTreeTerminationOptions) => Promise<unknown>;
|
|
51
|
+
};
|
|
52
|
+
export declare class CheckExecutionAbortedError extends Error {
|
|
53
|
+
constructor();
|
|
54
|
+
}
|
|
55
|
+
export declare class CheckProcessCoordinator {
|
|
56
|
+
private accepting;
|
|
57
|
+
private readonly active;
|
|
58
|
+
private readonly idleWaiters;
|
|
59
|
+
get activeCount(): number;
|
|
60
|
+
register(active: ActiveCheckProcess): (() => void) | null;
|
|
61
|
+
stopAccepting(): void;
|
|
62
|
+
terminateActive(options?: ProcessTreeTerminationOptions): Promise<void>;
|
|
63
|
+
waitForIdle(): Promise<void>;
|
|
64
|
+
private resolveIdleIfNeeded;
|
|
65
|
+
}
|
|
45
66
|
export declare function createNodeNpmCheckPlan(options: CreateNodeNpmCheckPlanOptions): CheckPlan;
|
|
46
67
|
export declare function executeCheckPlan(options: ExecuteCheckPlanOptions): Promise<ChecksExecutionResult>;
|
|
68
|
+
export {};
|
|
47
69
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/checks/index.js
CHANGED
|
@@ -2,6 +2,62 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { redactText } from "@tutti/shared/utils/redaction";
|
|
5
|
+
import { createManagedChildProcess, terminateManagedProcessTree, } from "../process-tree.js";
|
|
6
|
+
export class CheckExecutionAbortedError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super("Project checks were stopped because the Host is shutting down.");
|
|
9
|
+
this.name = "CheckExecutionAbortedError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class CheckProcessCoordinator {
|
|
13
|
+
accepting = true;
|
|
14
|
+
active = new Set();
|
|
15
|
+
idleWaiters = new Set();
|
|
16
|
+
get activeCount() {
|
|
17
|
+
return this.active.size;
|
|
18
|
+
}
|
|
19
|
+
register(active) {
|
|
20
|
+
if (!this.accepting) {
|
|
21
|
+
active.markAborted();
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
this.active.add(active);
|
|
25
|
+
let released = false;
|
|
26
|
+
return () => {
|
|
27
|
+
if (released) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
released = true;
|
|
31
|
+
this.active.delete(active);
|
|
32
|
+
this.resolveIdleIfNeeded();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
stopAccepting() {
|
|
36
|
+
this.accepting = false;
|
|
37
|
+
}
|
|
38
|
+
async terminateActive(options = {}) {
|
|
39
|
+
const active = [...this.active];
|
|
40
|
+
for (const entry of active) {
|
|
41
|
+
entry.markAborted();
|
|
42
|
+
}
|
|
43
|
+
await Promise.all(active.map((entry) => entry.terminate(options)));
|
|
44
|
+
}
|
|
45
|
+
waitForIdle() {
|
|
46
|
+
if (this.active.size === 0) {
|
|
47
|
+
return Promise.resolve();
|
|
48
|
+
}
|
|
49
|
+
return new Promise((resolve) => this.idleWaiters.add(resolve));
|
|
50
|
+
}
|
|
51
|
+
resolveIdleIfNeeded() {
|
|
52
|
+
if (this.active.size !== 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const resolve of this.idleWaiters) {
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
this.idleWaiters.clear();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
5
61
|
const DEFAULT_CHECK_TIMEOUT_MS = 120_000;
|
|
6
62
|
const MAX_OUTPUT_BYTES = 16 * 1024;
|
|
7
63
|
const DEFAULT_SKIP_REASON = "no_validation_command_found";
|
|
@@ -83,25 +139,71 @@ function commandEnvironment() {
|
|
|
83
139
|
}
|
|
84
140
|
function runCheckCommand(options) {
|
|
85
141
|
const startedAt = Date.now();
|
|
86
|
-
return new Promise((resolve) => {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
87
143
|
const child = spawn(options.command.command, options.command.args, {
|
|
88
144
|
cwd: options.workspaceRoot,
|
|
145
|
+
detached: process.platform !== "win32",
|
|
89
146
|
env: commandEnvironment(),
|
|
90
147
|
shell: false,
|
|
91
148
|
stdio: ["ignore", "pipe", "pipe"],
|
|
92
149
|
});
|
|
150
|
+
const managedProcess = createManagedChildProcess(child);
|
|
93
151
|
const stdout = [];
|
|
94
152
|
const stderr = [];
|
|
95
153
|
let timedOut = false;
|
|
154
|
+
let aborted = false;
|
|
155
|
+
let settled = false;
|
|
156
|
+
let terminationPromise;
|
|
157
|
+
const terminate = (termination) => {
|
|
158
|
+
terminationPromise ??= terminateManagedProcessTree(managedProcess, termination ?? options.termination);
|
|
159
|
+
return terminationPromise;
|
|
160
|
+
};
|
|
161
|
+
const release = options.processCoordinator?.register({
|
|
162
|
+
markAborted: () => {
|
|
163
|
+
aborted = true;
|
|
164
|
+
},
|
|
165
|
+
terminate,
|
|
166
|
+
});
|
|
167
|
+
const finish = (result) => {
|
|
168
|
+
if (settled) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
settled = true;
|
|
172
|
+
release?.();
|
|
173
|
+
if (aborted) {
|
|
174
|
+
reject(new CheckExecutionAbortedError());
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
resolve(result);
|
|
178
|
+
};
|
|
179
|
+
const failTermination = (error) => {
|
|
180
|
+
if (settled) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
settled = true;
|
|
184
|
+
reject(error instanceof Error
|
|
185
|
+
? error
|
|
186
|
+
: new Error("Check process termination failed without a structured error."));
|
|
187
|
+
};
|
|
188
|
+
const finishAfterTermination = (result) => {
|
|
189
|
+
if (terminationPromise === undefined) {
|
|
190
|
+
finish(result);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
void terminationPromise.then(() => finish(result), (error) => failTermination(error));
|
|
194
|
+
};
|
|
96
195
|
const timeout = setTimeout(() => {
|
|
97
196
|
timedOut = true;
|
|
98
|
-
|
|
197
|
+
void terminate().catch(failTermination);
|
|
99
198
|
}, options.timeoutMs);
|
|
199
|
+
if (release === null) {
|
|
200
|
+
void terminate().catch(failTermination);
|
|
201
|
+
}
|
|
100
202
|
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
101
203
|
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
102
204
|
child.on("error", (error) => {
|
|
103
205
|
clearTimeout(timeout);
|
|
104
|
-
|
|
206
|
+
finishAfterTermination({
|
|
105
207
|
id: options.command.id,
|
|
106
208
|
label: options.command.label,
|
|
107
209
|
command: options.command.command,
|
|
@@ -114,7 +216,7 @@ function runCheckCommand(options) {
|
|
|
114
216
|
});
|
|
115
217
|
child.on("close", (code) => {
|
|
116
218
|
clearTimeout(timeout);
|
|
117
|
-
|
|
219
|
+
finishAfterTermination({
|
|
118
220
|
id: options.command.id,
|
|
119
221
|
label: options.command.label,
|
|
120
222
|
command: options.command.command,
|
|
@@ -143,6 +245,10 @@ export async function executeCheckPlan(options) {
|
|
|
143
245
|
workspaceRoot: options.workspaceRoot,
|
|
144
246
|
command,
|
|
145
247
|
timeoutMs: options.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS,
|
|
248
|
+
...(options.termination === undefined ? {} : { termination: options.termination }),
|
|
249
|
+
...(options.processCoordinator === undefined
|
|
250
|
+
? {}
|
|
251
|
+
: { processCoordinator: options.processCoordinator }),
|
|
146
252
|
}));
|
|
147
253
|
}
|
|
148
254
|
const requiredCommands = options.plan.commands.filter((command) => command.required);
|
|
@@ -22,5 +22,6 @@ export declare function startControlPlaneTaskBoundFollowUpCheck(options: {
|
|
|
22
22
|
now: () => Date;
|
|
23
23
|
publishProcedureTransition: (state: ProcedureEngineState) => void;
|
|
24
24
|
startRunForTask: (runInput: StartRunForTaskInput) => void;
|
|
25
|
+
shouldContinue?: () => boolean;
|
|
25
26
|
}): boolean;
|
|
26
27
|
//# sourceMappingURL=follow-up-start.d.ts.map
|
|
@@ -19,7 +19,9 @@ export function startControlPlaneTaskBoundFollowUpCheck(options) {
|
|
|
19
19
|
now: options.now,
|
|
20
20
|
onTransition: (state) => {
|
|
21
21
|
options.publishProcedureTransition(state);
|
|
22
|
-
if (state.activity.status === "finished" &&
|
|
22
|
+
if (state.activity.status === "finished" &&
|
|
23
|
+
output?.decision === "resume" &&
|
|
24
|
+
options.shouldContinue?.() !== false) {
|
|
23
25
|
startRunFromFollowUpSuccessor({
|
|
24
26
|
store: options.store,
|
|
25
27
|
events: options.events,
|
|
@@ -22,6 +22,8 @@ export declare class Phase5ControlPlane {
|
|
|
22
22
|
private readonly referenceSummaryWaiters;
|
|
23
23
|
private pendingAllReferenceSummaries;
|
|
24
24
|
private referenceSummaryStartScheduled;
|
|
25
|
+
private closing;
|
|
26
|
+
private readonly asyncOwners;
|
|
25
27
|
private lastRecoveryConclusion;
|
|
26
28
|
constructor(options: Phase5ControlPlaneOptions);
|
|
27
29
|
getExecutionStatus(): ExecutionStatusProjection;
|
|
@@ -36,7 +38,9 @@ export declare class Phase5ControlPlane {
|
|
|
36
38
|
workflowRef?: WorkflowInvocationRef;
|
|
37
39
|
activityRef?: ActivityRef;
|
|
38
40
|
}): Promise<RefreshProjectBriefProjectionResult>;
|
|
39
|
-
|
|
41
|
+
beginShutdown(): void;
|
|
42
|
+
dispose(): Promise<void>;
|
|
43
|
+
waitForRepoInspectionIdle(): Promise<void>;
|
|
40
44
|
startTaskCompile(payload: SubmitWorklistPayload): ControlPlaneCommandResult<SubmitWorklistDisposition, SubmitWorklistResult>;
|
|
41
45
|
startContextSyncAnalysis(trigger?: ContextSyncWorkflowTrigger): ControlPlaneCommandResult<ContextSyncDisposition, ContextSyncResult>;
|
|
42
46
|
startReferenceSummaryRefresh(payload?: RefreshReferenceSummariesPayload): ControlPlaneCommandResult<RefreshReferenceSummariesDisposition, RefreshReferenceSummariesResult>;
|
|
@@ -44,6 +48,7 @@ export declare class Phase5ControlPlane {
|
|
|
44
48
|
sendClarificationRoundMessage(roundId: ClarificationRoundRef, payload: SendClarificationRoundMessagePayload, author: TrustedHumanAuthor): ControlPlaneCommandResult<SendClarificationRoundMessageDisposition, SendClarificationRoundMessageResult>;
|
|
45
49
|
submitClarificationRound(roundId: ClarificationRoundRef, payload: SubmitClarificationRoundPayload, commandId: IdempotencyKey): ControlPlaneCommandResult<SubmitClarificationRoundDisposition, SubmitClarificationRoundResult>;
|
|
46
50
|
waitForIdle(): Promise<void>;
|
|
51
|
+
private trackAsyncOwner;
|
|
47
52
|
private hasPendingReferenceSummaryRefresh;
|
|
48
53
|
private schedulePendingReferenceSummaryRefresh;
|
|
49
54
|
private startRunForTask;
|
|
@@ -36,6 +36,8 @@ export class Phase5ControlPlane {
|
|
|
36
36
|
referenceSummaryWaiters = new Map();
|
|
37
37
|
pendingAllReferenceSummaries = false;
|
|
38
38
|
referenceSummaryStartScheduled = false;
|
|
39
|
+
closing = false;
|
|
40
|
+
asyncOwners = new Set();
|
|
39
41
|
lastRecoveryConclusion = null;
|
|
40
42
|
constructor(options) {
|
|
41
43
|
this.store = options.store;
|
|
@@ -115,7 +117,7 @@ export class Phase5ControlPlane {
|
|
|
115
117
|
this.scratchpadAutoRefreshScheduler.notifySourceChanged();
|
|
116
118
|
}
|
|
117
119
|
refreshProjectBrief(options) {
|
|
118
|
-
return refreshProjectBriefProjection({
|
|
120
|
+
return this.trackAsyncOwner(refreshProjectBriefProjection({
|
|
119
121
|
store: this.store,
|
|
120
122
|
projectContext: this.projectContext,
|
|
121
123
|
reason: options.reason,
|
|
@@ -124,12 +126,46 @@ export class Phase5ControlPlane {
|
|
|
124
126
|
...(options.activityRef === undefined ? {} : { activityRef: options.activityRef }),
|
|
125
127
|
logger: this.logger,
|
|
126
128
|
now: this.now,
|
|
127
|
-
});
|
|
129
|
+
}));
|
|
128
130
|
}
|
|
129
|
-
|
|
131
|
+
beginShutdown() {
|
|
132
|
+
if (this.closing) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.closing = true;
|
|
130
136
|
this.scratchpadAutoRefreshScheduler.dispose();
|
|
137
|
+
this.pendingAllReferenceSummaries = false;
|
|
138
|
+
this.pendingReferenceSummaryPaths.clear();
|
|
139
|
+
}
|
|
140
|
+
dispose() {
|
|
141
|
+
this.beginShutdown();
|
|
142
|
+
return this.waitForIdle();
|
|
143
|
+
}
|
|
144
|
+
async waitForRepoInspectionIdle() {
|
|
145
|
+
for (;;) {
|
|
146
|
+
const waits = [];
|
|
147
|
+
if (this.readActiveRun() !== null) {
|
|
148
|
+
waits.push(this.runEngine.waitForIdle());
|
|
149
|
+
}
|
|
150
|
+
if (this.readActiveForegroundProcedure() !== null) {
|
|
151
|
+
waits.push(this.engine.waitForLaneIdle("foreground"));
|
|
152
|
+
}
|
|
153
|
+
if (waits.length === 0) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
await Promise.all(waits);
|
|
157
|
+
}
|
|
131
158
|
}
|
|
132
159
|
startTaskCompile(payload) {
|
|
160
|
+
const activeRun = this.readActiveRun();
|
|
161
|
+
if (activeRun !== null) {
|
|
162
|
+
return {
|
|
163
|
+
disposition: {
|
|
164
|
+
kind: "already_running",
|
|
165
|
+
activity_ref: activeRun.activity_ref,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
133
169
|
let tasksAdded = false;
|
|
134
170
|
return startControlPlaneTaskCompile({
|
|
135
171
|
engine: this.engine,
|
|
@@ -142,7 +178,8 @@ export class Phase5ControlPlane {
|
|
|
142
178
|
now: this.now,
|
|
143
179
|
publishProcedureTransition: (state) => {
|
|
144
180
|
this.publishProcedureTransition(state);
|
|
145
|
-
if (
|
|
181
|
+
if (!this.closing &&
|
|
182
|
+
tasksAdded &&
|
|
146
183
|
state.activity.status === "finished" &&
|
|
147
184
|
state.activity.workflow_kind === "task_compile") {
|
|
148
185
|
this.tryRunTaskArrivalScheduler();
|
|
@@ -156,6 +193,7 @@ export class Phase5ControlPlane {
|
|
|
156
193
|
onTasksAdded: () => {
|
|
157
194
|
tasksAdded = true;
|
|
158
195
|
},
|
|
196
|
+
shouldContinue: () => !this.closing,
|
|
159
197
|
});
|
|
160
198
|
}
|
|
161
199
|
startContextSyncAnalysis(trigger = { kind: "manual" }) {
|
|
@@ -261,25 +299,36 @@ export class Phase5ControlPlane {
|
|
|
261
299
|
while (true) {
|
|
262
300
|
await this.engine.waitForIdle();
|
|
263
301
|
await this.runEngine.waitForIdle();
|
|
302
|
+
await Promise.allSettled([...this.asyncOwners]);
|
|
264
303
|
await Promise.resolve();
|
|
265
304
|
if (this.readAnyActiveActivity() === null &&
|
|
266
|
-
|
|
305
|
+
this.asyncOwners.size === 0 &&
|
|
306
|
+
(this.closing || !this.hasPendingReferenceSummaryRefresh())) {
|
|
267
307
|
return;
|
|
268
308
|
}
|
|
269
|
-
this.
|
|
309
|
+
if (!this.closing) {
|
|
310
|
+
this.schedulePendingReferenceSummaryRefresh();
|
|
311
|
+
}
|
|
270
312
|
}
|
|
271
313
|
}
|
|
314
|
+
trackAsyncOwner(owner) {
|
|
315
|
+
this.asyncOwners.add(owner);
|
|
316
|
+
void owner.then(() => this.asyncOwners.delete(owner), () => this.asyncOwners.delete(owner));
|
|
317
|
+
return owner;
|
|
318
|
+
}
|
|
272
319
|
hasPendingReferenceSummaryRefresh() {
|
|
273
320
|
return this.pendingAllReferenceSummaries || this.pendingReferenceSummaryPaths.size !== 0;
|
|
274
321
|
}
|
|
275
322
|
schedulePendingReferenceSummaryRefresh() {
|
|
276
|
-
if (
|
|
323
|
+
if (this.closing ||
|
|
324
|
+
!this.hasPendingReferenceSummaryRefresh() ||
|
|
325
|
+
this.referenceSummaryStartScheduled) {
|
|
277
326
|
return;
|
|
278
327
|
}
|
|
279
328
|
this.referenceSummaryStartScheduled = true;
|
|
280
329
|
queueMicrotask(() => {
|
|
281
330
|
this.referenceSummaryStartScheduled = false;
|
|
282
|
-
if (this.hasPendingReferenceSummaryRefresh()) {
|
|
331
|
+
if (!this.closing && this.hasPendingReferenceSummaryRefresh()) {
|
|
283
332
|
void this.tryStartPendingReferenceSummaryRefresh();
|
|
284
333
|
}
|
|
285
334
|
});
|
|
@@ -417,6 +466,9 @@ export class Phase5ControlPlane {
|
|
|
417
466
|
}
|
|
418
467
|
}
|
|
419
468
|
tryRunTaskArrivalScheduler() {
|
|
469
|
+
if (this.closing) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
420
472
|
const result = runControlPlaneSchedulerForTaskArrival({
|
|
421
473
|
store: this.store,
|
|
422
474
|
resolveRunPipeline: this.resolveRunPipeline,
|
|
@@ -446,6 +498,9 @@ export class Phase5ControlPlane {
|
|
|
446
498
|
};
|
|
447
499
|
}
|
|
448
500
|
maybeStartContextSyncAfterWorklistCompletion() {
|
|
501
|
+
if (this.closing) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
449
504
|
const trigger = this.worklistCompletionContextSyncTrigger();
|
|
450
505
|
if (trigger === undefined || this.readSchedulerBlocker() !== null) {
|
|
451
506
|
return;
|
|
@@ -466,7 +521,8 @@ export class Phase5ControlPlane {
|
|
|
466
521
|
now: this.now,
|
|
467
522
|
publishProcedureTransition: (state) => {
|
|
468
523
|
this.publishProcedureTransition(state);
|
|
469
|
-
if (
|
|
524
|
+
if (!this.closing &&
|
|
525
|
+
tasksAdded &&
|
|
470
526
|
state.activity.status === "finished" &&
|
|
471
527
|
state.activity.workflow_kind === "task_compile") {
|
|
472
528
|
this.tryRunTaskArrivalScheduler();
|
|
@@ -492,8 +548,11 @@ export class Phase5ControlPlane {
|
|
|
492
548
|
now: this.now,
|
|
493
549
|
publishProcedureTransition: (state) => this.publishProcedureTransition(state),
|
|
494
550
|
startRunForTask: (runInput) => {
|
|
495
|
-
|
|
551
|
+
if (!this.closing) {
|
|
552
|
+
void this.startRunForTask(runInput);
|
|
553
|
+
}
|
|
496
554
|
},
|
|
555
|
+
shouldContinue: () => !this.closing,
|
|
497
556
|
});
|
|
498
557
|
}
|
|
499
558
|
publishProcedureTransition(state) {
|
|
@@ -521,7 +580,9 @@ export class Phase5ControlPlane {
|
|
|
521
580
|
}, "Reference summary continuation failed");
|
|
522
581
|
}
|
|
523
582
|
}
|
|
524
|
-
this.
|
|
583
|
+
if (!this.closing) {
|
|
584
|
+
this.schedulePendingReferenceSummaryRefresh();
|
|
585
|
+
}
|
|
525
586
|
}
|
|
526
587
|
}
|
|
527
588
|
logProcedureTransition(state) {
|
|
@@ -1,77 +1,35 @@
|
|
|
1
|
-
const MAX_ERROR_MESSAGE_LENGTH = 1_000;
|
|
2
|
-
const MAX_DETAIL_STRING_LENGTH = 1_000;
|
|
3
1
|
const MAX_DETAIL_ARRAY_ITEMS = 20;
|
|
4
|
-
const
|
|
2
|
+
const SAFE_DETAIL_KEYS = [
|
|
3
|
+
"turn_status",
|
|
4
|
+
"turn_error_code",
|
|
5
|
+
"turn_error_http_status",
|
|
6
|
+
"turn_timeout_ms",
|
|
7
|
+
"last_activity_at",
|
|
8
|
+
"notification_count",
|
|
9
|
+
"server_request_methods",
|
|
10
|
+
"notification_methods",
|
|
11
|
+
];
|
|
5
12
|
function isRecord(value) {
|
|
6
13
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
14
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return normalized;
|
|
12
|
-
}
|
|
13
|
-
return `${normalized.slice(0, maxLength)}...`;
|
|
14
|
-
}
|
|
15
|
-
function boundedDetailValue(value, seen = new WeakSet()) {
|
|
16
|
-
if (value === null ||
|
|
17
|
-
typeof value === "number" ||
|
|
18
|
-
typeof value === "boolean" ||
|
|
19
|
-
typeof value === "undefined") {
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
if (typeof value === "string") {
|
|
23
|
-
return compactString(value, MAX_DETAIL_STRING_LENGTH);
|
|
24
|
-
}
|
|
25
|
-
if (typeof value === "bigint") {
|
|
26
|
-
return value.toString();
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray(value)) {
|
|
29
|
-
const items = value
|
|
30
|
-
.slice(0, MAX_DETAIL_ARRAY_ITEMS)
|
|
31
|
-
.map((item) => boundedDetailValue(item, seen));
|
|
32
|
-
if (value.length > MAX_DETAIL_ARRAY_ITEMS) {
|
|
33
|
-
items.push(`[${value.length - MAX_DETAIL_ARRAY_ITEMS} more items]`);
|
|
34
|
-
}
|
|
35
|
-
return items;
|
|
36
|
-
}
|
|
37
|
-
if (value instanceof Error) {
|
|
38
|
-
return {
|
|
39
|
-
name: value.name,
|
|
40
|
-
message: compactString(value.message, MAX_DETAIL_STRING_LENGTH),
|
|
41
|
-
};
|
|
15
|
+
function errorDetails(error) {
|
|
16
|
+
if (!isRecord(error) || !isRecord(error.details)) {
|
|
17
|
+
return undefined;
|
|
42
18
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const bounded = {};
|
|
50
|
-
for (const [key, nested] of entries.slice(0, MAX_DETAIL_OBJECT_KEYS)) {
|
|
51
|
-
const boundedNested = boundedDetailValue(nested, seen);
|
|
52
|
-
if (boundedNested !== undefined) {
|
|
53
|
-
bounded[key] = boundedNested;
|
|
54
|
-
}
|
|
19
|
+
const safe = {};
|
|
20
|
+
for (const key of SAFE_DETAIL_KEYS) {
|
|
21
|
+
const value = error.details[key];
|
|
22
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
23
|
+
safe[key] = value;
|
|
24
|
+
continue;
|
|
55
25
|
}
|
|
56
|
-
if (
|
|
57
|
-
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
safe[key] = value
|
|
28
|
+
.filter((item) => typeof item === "string")
|
|
29
|
+
.slice(0, MAX_DETAIL_ARRAY_ITEMS);
|
|
58
30
|
}
|
|
59
|
-
seen.delete(value);
|
|
60
|
-
return bounded;
|
|
61
|
-
}
|
|
62
|
-
if (typeof value === "function") {
|
|
63
|
-
return value.name.trim().length === 0 ? "[Function]" : `[Function:${value.name}]`;
|
|
64
|
-
}
|
|
65
|
-
if (typeof value === "symbol") {
|
|
66
|
-
return value.description === undefined ? "[Symbol]" : `[Symbol:${value.description}]`;
|
|
67
|
-
}
|
|
68
|
-
return "[Unsupported]";
|
|
69
|
-
}
|
|
70
|
-
function errorDetails(error) {
|
|
71
|
-
if (!isRecord(error) || !("details" in error)) {
|
|
72
|
-
return undefined;
|
|
73
31
|
}
|
|
74
|
-
return
|
|
32
|
+
return Object.keys(safe).length === 0 ? undefined : safe;
|
|
75
33
|
}
|
|
76
34
|
function errorCode(error) {
|
|
77
35
|
if (!isRecord(error) || typeof error.code !== "string") {
|
|
@@ -85,7 +43,6 @@ export function logProcedureExecutionError(input) {
|
|
|
85
43
|
workflow_ref: input.workflowRef,
|
|
86
44
|
workflow_kind: input.workflowKind,
|
|
87
45
|
error_name: input.error instanceof Error ? input.error.name : "NonError",
|
|
88
|
-
error_message: compactString(input.error instanceof Error ? input.error.message : String(input.error), MAX_ERROR_MESSAGE_LENGTH),
|
|
89
46
|
...(errorCode(input.error) === undefined ? {} : { error_code: errorCode(input.error) }),
|
|
90
47
|
...(errorDetails(input.error) === undefined
|
|
91
48
|
? {}
|