@zachwill/pi-orchestrate 0.1.1 → 0.2.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 +3 -0
- package/extension/catalog.ts +176 -157
- package/extension/domain.ts +5 -1
- package/extension/host.ts +2 -2
- package/extension/presentation.ts +139 -104
- package/extension/runtime.ts +150 -63
- package/extension/scheduler.ts +44 -25
- package/extension/tools.ts +63 -23
- package/extension/worker-session.ts +488 -295
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,6 +80,7 @@ Frontmatter supports these fields:
|
|
|
80
80
|
- `tools` and `lifecycle` are required. Grant the smallest useful tool set.
|
|
81
81
|
- `model` is optional. When omitted, the worker inherits the parent model active when dispatched.
|
|
82
82
|
- `thinking`, `skills`, and `compaction` are optional.
|
|
83
|
+
- Omitted `skills` uses Pi's normal discovered skills. A nonempty `skills` list is an exact name allowlist, and `skills: []` disables skills.
|
|
83
84
|
- `lifecycle` must be exactly `one-shot` or `reusable`.
|
|
84
85
|
- The Markdown body must be nonempty.
|
|
85
86
|
|
|
@@ -93,4 +94,6 @@ Use one-shot workers for bounded investigation, review, and implementation. Use
|
|
|
93
94
|
|
|
94
95
|
Worker sessions are isolated from the parent's conversational context, but they run in-process and are not sandboxes. They share the parent process's filesystem and environment permissions. Treat worker prompts, optional skills, models, and tool grants as trusted code.
|
|
95
96
|
|
|
97
|
+
Workers use regular persisted Pi global settings, authentication, packages, extensions, skills, and context. Trusted projects also contribute their project settings and resources; untrusted projects do not. Extensions are active in print mode for the complete worker lifecycle, including resource discovery and provider request hooks. Pi Orchestrate excludes its own package before child extension factories execute, so workers remain direct children while other configured extensions—including provider integrations such as `@benvargas/pi-claude-code-use`—load normally. Worker definitions still provide the exact bounded tool allowlist.
|
|
98
|
+
|
|
96
99
|
Pi Orchestrate performs no automatic filesystem writes. A worker writes only when its instructions and granted tools cause it to do so. Parallel workers must have non-overlapping write scopes, and the parent must inspect and verify their changes.
|
package/extension/catalog.ts
CHANGED
|
@@ -6,41 +6,79 @@ import {
|
|
|
6
6
|
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
7
7
|
import { basename, extname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { Result, Schema, SchemaGetter, type SchemaIssue } from "effect";
|
|
9
10
|
import type {
|
|
10
11
|
CatalogDiagnostic,
|
|
11
|
-
SupportedToolName,
|
|
12
12
|
WorkerCatalog,
|
|
13
13
|
WorkerDefinition,
|
|
14
|
-
WorkerLifecycle,
|
|
15
14
|
WorkerSourceKind,
|
|
16
15
|
} from "./domain.js";
|
|
17
|
-
import { isSupportedToolName } from "./domain.js";
|
|
16
|
+
import { isSupportedToolName, SUPPORTED_TOOL_NAMES } from "./domain.js";
|
|
18
17
|
|
|
19
18
|
const MAX_WORKER_BYTES = 64 * 1024;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
function isThinkingLevel(value: string): value is NonNullable<WorkerDefinition["thinking"]> {
|
|
41
|
-
return THINKING_LEVELS.has(value);
|
|
19
|
+
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
20
|
+
|
|
21
|
+
const RequiredText = Schema.Trim.check(Schema.isNonEmpty());
|
|
22
|
+
const StringListInput = Schema.Union([Schema.String, Schema.Array(Schema.String)]);
|
|
23
|
+
|
|
24
|
+
function commaList<Item extends Schema.Constraint & { readonly Encoded: string }>(
|
|
25
|
+
item: Item,
|
|
26
|
+
allowEmpty = false,
|
|
27
|
+
) {
|
|
28
|
+
const items = allowEmpty
|
|
29
|
+
? Schema.Array(item)
|
|
30
|
+
: Schema.Array(item).check(Schema.isMinLength(1));
|
|
31
|
+
return StringListInput.pipe(
|
|
32
|
+
Schema.decodeTo(items, {
|
|
33
|
+
decode: SchemaGetter.transform((value) =>
|
|
34
|
+
typeof value === "string" ? value.split(",").map((entry) => entry.trim()) : value,
|
|
35
|
+
),
|
|
36
|
+
encode: SchemaGetter.transform((value) => value),
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
42
39
|
}
|
|
43
40
|
|
|
41
|
+
const ModelCoordinate = Schema.Trim.check(Schema.isPattern(/^[^/\s]+\/\S+$/)).pipe(
|
|
42
|
+
Schema.decodeTo(
|
|
43
|
+
Schema.Struct({ provider: Schema.NonEmptyString, modelId: Schema.NonEmptyString }),
|
|
44
|
+
{
|
|
45
|
+
decode: SchemaGetter.transform((coordinate) => {
|
|
46
|
+
const separator = coordinate.indexOf("/");
|
|
47
|
+
return {
|
|
48
|
+
provider: coordinate.slice(0, separator),
|
|
49
|
+
modelId: coordinate.slice(separator + 1),
|
|
50
|
+
};
|
|
51
|
+
}),
|
|
52
|
+
encode: SchemaGetter.transform(({ provider, modelId }) => `${provider}/${modelId}`),
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const NonNegativeInteger = Schema.Number.check(
|
|
58
|
+
Schema.isInt(),
|
|
59
|
+
Schema.isGreaterThanOrEqualTo(0),
|
|
60
|
+
);
|
|
61
|
+
const Compaction = Schema.Struct({
|
|
62
|
+
enabled: Schema.optionalKey(Schema.Boolean),
|
|
63
|
+
reserveTokens: Schema.optionalKey(NonNegativeInteger),
|
|
64
|
+
keepRecentTokens: Schema.optionalKey(NonNegativeInteger),
|
|
65
|
+
});
|
|
66
|
+
const ThinkingLevel = Schema.Trim.pipe(Schema.decodeTo(Schema.Literals(THINKING_LEVELS)));
|
|
67
|
+
const WorkerFrontmatter = Schema.Struct({
|
|
68
|
+
name: RequiredText,
|
|
69
|
+
description: RequiredText,
|
|
70
|
+
model: Schema.optionalKey(ModelCoordinate),
|
|
71
|
+
thinking: Schema.optionalKey(ThinkingLevel),
|
|
72
|
+
tools: commaList(Schema.Literals(SUPPORTED_TOOL_NAMES)),
|
|
73
|
+
skills: Schema.optionalKey(commaList(Schema.NonEmptyString, true)),
|
|
74
|
+
compaction: Schema.optionalKey(Compaction),
|
|
75
|
+
lifecycle: Schema.Literals(["one-shot", "reusable"]),
|
|
76
|
+
});
|
|
77
|
+
const decodeWorkerFrontmatter = Schema.decodeUnknownResult(WorkerFrontmatter, {
|
|
78
|
+
errors: "all",
|
|
79
|
+
onExcessProperty: "error",
|
|
80
|
+
});
|
|
81
|
+
|
|
44
82
|
export interface CatalogFileStat {
|
|
45
83
|
readonly size: number;
|
|
46
84
|
isFile(): boolean;
|
|
@@ -65,18 +103,6 @@ interface CatalogSource {
|
|
|
65
103
|
readonly directory: string;
|
|
66
104
|
}
|
|
67
105
|
|
|
68
|
-
interface WorkerFrontmatter {
|
|
69
|
-
readonly name?: unknown;
|
|
70
|
-
readonly description?: unknown;
|
|
71
|
-
readonly model?: unknown;
|
|
72
|
-
readonly thinking?: unknown;
|
|
73
|
-
readonly tools?: unknown;
|
|
74
|
-
readonly skills?: unknown;
|
|
75
|
-
readonly compaction?: unknown;
|
|
76
|
-
readonly lifecycle?: unknown;
|
|
77
|
-
readonly [field: string]: unknown;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
106
|
const productionFileSystem: CatalogFileSystem = {
|
|
81
107
|
readDirectory: (directory) => readdirSync(directory),
|
|
82
108
|
inspect: (path) => lstatSync(path),
|
|
@@ -101,98 +127,128 @@ function isMissingPath(error: unknown): boolean {
|
|
|
101
127
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
throw new Error(`frontmatter field '${field}' must be a non-empty string`);
|
|
107
|
-
}
|
|
108
|
-
return value.trim();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function optionalString(value: unknown, field: string): string | undefined {
|
|
112
|
-
if (value === undefined) return undefined;
|
|
113
|
-
return requiredString(value, field);
|
|
130
|
+
interface UnexpectedPath {
|
|
131
|
+
readonly path: readonly PropertyKey[];
|
|
114
132
|
}
|
|
115
133
|
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
function collectUnexpectedPaths(
|
|
135
|
+
issue: SchemaIssue.Issue,
|
|
136
|
+
parentPath: readonly PropertyKey[] = [],
|
|
137
|
+
): UnexpectedPath[] {
|
|
138
|
+
switch (issue._tag) {
|
|
139
|
+
case "Pointer":
|
|
140
|
+
return collectUnexpectedPaths(issue.issue, [...parentPath, ...issue.path]);
|
|
141
|
+
case "Composite":
|
|
142
|
+
case "AnyOf":
|
|
143
|
+
return issue.issues.flatMap((child) => collectUnexpectedPaths(child, parentPath));
|
|
144
|
+
case "Encoding":
|
|
145
|
+
case "Filter":
|
|
146
|
+
return collectUnexpectedPaths(issue.issue, parentPath);
|
|
147
|
+
case "UnexpectedKey":
|
|
148
|
+
return [{ path: parentPath }];
|
|
149
|
+
default:
|
|
150
|
+
return [];
|
|
133
151
|
}
|
|
134
|
-
return strings;
|
|
135
152
|
}
|
|
136
153
|
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
for (const tool of stringList(value, "tools", true) ?? []) {
|
|
140
|
-
if (!isSupportedToolName(tool)) throw new Error(`unsupported tool '${tool}'`);
|
|
141
|
-
tools.push(tool);
|
|
142
|
-
}
|
|
143
|
-
return tools;
|
|
154
|
+
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
155
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
144
156
|
}
|
|
145
157
|
|
|
146
|
-
function
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
throw new Error(`frontmatter field '${field}' must be a boolean`);
|
|
150
|
-
}
|
|
151
|
-
return value;
|
|
158
|
+
function fieldValue(frontmatter: unknown, field: string): unknown {
|
|
159
|
+
if (!isUnknownRecord(frontmatter)) return undefined;
|
|
160
|
+
return field in frontmatter ? frontmatter[field] : undefined;
|
|
152
161
|
}
|
|
153
162
|
|
|
154
|
-
function
|
|
155
|
-
if (value
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
return value;
|
|
163
|
+
function listItems(value: unknown): readonly unknown[] {
|
|
164
|
+
if (typeof value === "string") return value.split(",").map((item) => item.trim());
|
|
165
|
+
return Array.isArray(value) ? value : [];
|
|
159
166
|
}
|
|
160
167
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
168
|
+
function schemaDiagnostic(error: Schema.SchemaError, frontmatter: unknown): string {
|
|
169
|
+
const unexpected = collectUnexpectedPaths(error.issue);
|
|
170
|
+
const frontmatterFields = unexpected
|
|
171
|
+
.filter(({ path }) => path.length === 1 && typeof path[0] === "string")
|
|
172
|
+
.map(({ path }) => String(path[0]))
|
|
173
|
+
.sort(compareText);
|
|
174
|
+
if (frontmatterFields.length > 0) {
|
|
175
|
+
return `unknown frontmatter field${frontmatterFields.length === 1 ? "" : "s"}: ${frontmatterFields.join(", ")}`;
|
|
165
176
|
}
|
|
166
177
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
178
|
+
const compactionFields = unexpected
|
|
179
|
+
.filter(
|
|
180
|
+
({ path }) => path.length === 2 && path[0] === "compaction" && typeof path[1] === "string",
|
|
181
|
+
)
|
|
182
|
+
.map(({ path }) => String(path[1]))
|
|
183
|
+
.sort(compareText);
|
|
184
|
+
|
|
185
|
+
if (typeof frontmatter !== "object" || frontmatter === null || Array.isArray(frontmatter)) {
|
|
186
|
+
return "frontmatter must be a mapping";
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
const orderedFields = [
|
|
190
|
+
"name",
|
|
191
|
+
"description",
|
|
192
|
+
"model",
|
|
193
|
+
"thinking",
|
|
194
|
+
"tools",
|
|
195
|
+
"skills",
|
|
196
|
+
"compaction",
|
|
197
|
+
"lifecycle",
|
|
198
|
+
];
|
|
199
|
+
const message = error.message;
|
|
200
|
+
const field = orderedFields.find((candidate) => message.includes(`["${candidate}"]`));
|
|
201
|
+
const value = field === undefined ? undefined : fieldValue(frontmatter, field);
|
|
181
202
|
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
(typeof reserveTokens !== "number" || !Number.isSafeInteger(reserveTokens) || reserveTokens < 0)
|
|
185
|
-
) {
|
|
186
|
-
throw new Error("frontmatter field 'compaction.reserveTokens' must be a non-negative integer");
|
|
203
|
+
if (field === "name" || field === "description") {
|
|
204
|
+
return `frontmatter field '${field}' must be a non-empty string`;
|
|
187
205
|
}
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
if (field === "model") {
|
|
207
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
208
|
+
return "frontmatter field 'model' must be a non-empty string";
|
|
209
|
+
}
|
|
210
|
+
return "frontmatter field 'model' must use provider/model format";
|
|
193
211
|
}
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
if (field === "thinking") {
|
|
213
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
214
|
+
return "frontmatter field 'thinking' must be a non-empty string";
|
|
215
|
+
}
|
|
216
|
+
return `unsupported thinking level '${value.trim()}'`;
|
|
217
|
+
}
|
|
218
|
+
if (field === "tools") {
|
|
219
|
+
const items = listItems(value);
|
|
220
|
+
const validList = items.length > 0 && items.every(
|
|
221
|
+
(item) => typeof item === "string" && item !== "",
|
|
222
|
+
);
|
|
223
|
+
const unsupported = validList ? items.find(
|
|
224
|
+
(item) => typeof item === "string" && !isSupportedToolName(item),
|
|
225
|
+
) : undefined;
|
|
226
|
+
if (typeof unsupported === "string") return `unsupported tool '${unsupported}'`;
|
|
227
|
+
if (value === undefined) return "frontmatter field 'tools' is required";
|
|
228
|
+
return "frontmatter field 'tools' must be a non-empty comma string or string array";
|
|
229
|
+
}
|
|
230
|
+
if (field === "skills") {
|
|
231
|
+
return "frontmatter field 'skills' must be a comma string or string array";
|
|
232
|
+
}
|
|
233
|
+
if (field === "compaction") {
|
|
234
|
+
if (compactionFields.length > 0) {
|
|
235
|
+
return `unknown compaction field${compactionFields.length === 1 ? "" : "s"}: ${compactionFields.join(", ")}`;
|
|
236
|
+
}
|
|
237
|
+
if (message.includes('["enabled"]')) {
|
|
238
|
+
return "frontmatter field 'compaction.enabled' must be a boolean";
|
|
239
|
+
}
|
|
240
|
+
if (message.includes('["reserveTokens"]')) {
|
|
241
|
+
return "frontmatter field 'compaction.reserveTokens' must be a non-negative integer";
|
|
242
|
+
}
|
|
243
|
+
if (message.includes('["keepRecentTokens"]')) {
|
|
244
|
+
return "frontmatter field 'compaction.keepRecentTokens' must be a non-negative integer";
|
|
245
|
+
}
|
|
246
|
+
return "frontmatter field 'compaction' must be a mapping";
|
|
247
|
+
}
|
|
248
|
+
if (field === "lifecycle") {
|
|
249
|
+
return "frontmatter field 'lifecycle' must be 'one-shot' or 'reusable'";
|
|
250
|
+
}
|
|
251
|
+
return "invalid worker definition";
|
|
196
252
|
}
|
|
197
253
|
|
|
198
254
|
function parseWorker(
|
|
@@ -200,65 +256,28 @@ function parseWorker(
|
|
|
200
256
|
source: WorkerSourceKind,
|
|
201
257
|
content: string,
|
|
202
258
|
): WorkerDefinition {
|
|
203
|
-
let parsed: ReturnType<typeof parseFrontmatter
|
|
259
|
+
let parsed: ReturnType<typeof parseFrontmatter>;
|
|
204
260
|
try {
|
|
205
|
-
parsed = parseFrontmatter
|
|
261
|
+
parsed = parseFrontmatter(content);
|
|
206
262
|
} catch {
|
|
207
263
|
throw new Error("frontmatter is not valid YAML");
|
|
208
264
|
}
|
|
209
265
|
|
|
210
266
|
const { frontmatter, body } = parsed;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const unknownFields = Object.keys(frontmatter)
|
|
216
|
-
.filter((field) => !KNOWN_FIELDS.has(field))
|
|
217
|
-
.sort(compareText);
|
|
218
|
-
if (unknownFields.length > 0) {
|
|
219
|
-
throw new Error(
|
|
220
|
-
`unknown frontmatter field${unknownFields.length === 1 ? "" : "s"}: ${unknownFields.join(", ")}`,
|
|
221
|
-
);
|
|
267
|
+
const decoded = decodeWorkerFrontmatter(frontmatter);
|
|
268
|
+
if (Result.isFailure(decoded)) {
|
|
269
|
+
throw new Error(schemaDiagnostic(decoded.failure, frontmatter));
|
|
222
270
|
}
|
|
223
271
|
|
|
224
|
-
const
|
|
272
|
+
const worker = decoded.success;
|
|
225
273
|
const expectedName = basename(filePath, extname(filePath));
|
|
226
|
-
if (name !== expectedName) {
|
|
227
|
-
throw new Error(`frontmatter name '${name}' must match basename '${expectedName}'`);
|
|
274
|
+
if (worker.name !== expectedName) {
|
|
275
|
+
throw new Error(`frontmatter name '${worker.name}' must match basename '${expectedName}'`);
|
|
228
276
|
}
|
|
229
|
-
|
|
230
|
-
const description = requiredString(frontmatter.description, "description");
|
|
231
|
-
const model = optionalString(frontmatter.model, "model");
|
|
232
|
-
if (model !== undefined && !/^[^/\s]+\/\S+$/.test(model)) {
|
|
233
|
-
throw new Error("frontmatter field 'model' must use provider/model format");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const thinking = optionalString(frontmatter.thinking, "thinking");
|
|
237
|
-
if (thinking !== undefined && !isThinkingLevel(thinking)) {
|
|
238
|
-
throw new Error(`unsupported thinking level '${thinking}'`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const tools = parseTools(frontmatter.tools);
|
|
242
|
-
const skills = stringList(frontmatter.skills, "skills", false);
|
|
243
|
-
const compaction = parseCompaction(frontmatter.compaction);
|
|
244
|
-
const lifecycle = parseLifecycle(frontmatter.lifecycle);
|
|
245
277
|
if (body.trim() === "") throw new Error("worker prompt body must not be empty");
|
|
246
278
|
|
|
247
279
|
return {
|
|
248
|
-
|
|
249
|
-
description,
|
|
250
|
-
model:
|
|
251
|
-
model === undefined
|
|
252
|
-
? undefined
|
|
253
|
-
: {
|
|
254
|
-
provider: model.slice(0, model.indexOf("/")),
|
|
255
|
-
modelId: model.slice(model.indexOf("/") + 1),
|
|
256
|
-
},
|
|
257
|
-
thinking,
|
|
258
|
-
tools,
|
|
259
|
-
skills: skills ?? [],
|
|
260
|
-
compaction,
|
|
261
|
-
lifecycle,
|
|
280
|
+
...worker,
|
|
262
281
|
systemPrompt: body,
|
|
263
282
|
source: { kind: source, filePath },
|
|
264
283
|
};
|
package/extension/domain.ts
CHANGED
|
@@ -45,7 +45,7 @@ export interface WorkerDefinition {
|
|
|
45
45
|
readonly systemPrompt: string;
|
|
46
46
|
readonly lifecycle: WorkerLifecycle;
|
|
47
47
|
readonly tools: readonly SupportedToolName[];
|
|
48
|
-
readonly skills
|
|
48
|
+
readonly skills?: readonly string[];
|
|
49
49
|
readonly model?: WorkerModel;
|
|
50
50
|
readonly thinking?: ThinkingLevel;
|
|
51
51
|
readonly compaction?: WorkerCompaction;
|
|
@@ -160,6 +160,9 @@ export interface WorkerUsage {
|
|
|
160
160
|
readonly turns: number;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/** Direction of the most recent message across the worker/model boundary. */
|
|
164
|
+
export type WorkerMessageDirection = "to-model" | "from-model";
|
|
165
|
+
|
|
163
166
|
export const EMPTY_WORKER_USAGE: WorkerUsage = Object.freeze({
|
|
164
167
|
input: 0,
|
|
165
168
|
output: 0,
|
|
@@ -231,6 +234,7 @@ export interface WorkerRecord {
|
|
|
231
234
|
readonly startedAt: number;
|
|
232
235
|
readonly settledAt?: number;
|
|
233
236
|
readonly activity?: string;
|
|
237
|
+
readonly messageDirection?: WorkerMessageDirection;
|
|
234
238
|
readonly outcome?: WorkerOutcome;
|
|
235
239
|
readonly sessionFile?: string;
|
|
236
240
|
}
|
package/extension/host.ts
CHANGED
|
@@ -22,7 +22,7 @@ interface AttachmentAwareProcessHost extends ProcessHost {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
interface OwnedProcessHost extends AttachmentAwareProcessHost {
|
|
25
|
-
readonly unsubscribeSettlement
|
|
25
|
+
readonly unsubscribeSettlement?: () => void;
|
|
26
26
|
destroyPromise?: Promise<void>;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -99,7 +99,7 @@ export async function destroyProcessHost(host: ProcessHost): Promise<void> {
|
|
|
99
99
|
await ownedHost.runtime.shutdown();
|
|
100
100
|
} finally {
|
|
101
101
|
ownedHost.delivery.clear();
|
|
102
|
-
ownedHost.unsubscribeSettlement();
|
|
102
|
+
ownedHost.unsubscribeSettlement?.();
|
|
103
103
|
const global = processGlobal();
|
|
104
104
|
if (global[PROCESS_HOST_KEY] === ownedHost) {
|
|
105
105
|
delete global[PROCESS_HOST_KEY];
|