@tt-a1i/openpi 0.4.0 → 0.6.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 +116 -46
- package/SETUP.md +29 -7
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +155 -0
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +38 -3
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +484 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +47 -24
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +12 -2
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +65 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +82 -6
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +138 -130
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +457 -21
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +83 -34
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +313 -62
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +450 -70
- package/extensions/subagents/src/domain.ts +21 -1
- package/extensions/subagents/src/manager.ts +39 -2
- package/extensions/subagents/src/prompt.ts +49 -7
- package/extensions/subagents/src/result-artifact.ts +36 -0
- package/extensions/subagents/src/result-delivery.ts +39 -14
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +16 -45
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +257 -0
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +459 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +175 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +501 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +79 -5
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +70 -16
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +214 -76
- package/extensions/workflows/retention.ts +599 -0
- package/extensions/workflows/runner.ts +389 -345
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +34 -14
- package/skills/subagents/REFERENCE.md +190 -0
- package/skills/subagents/SKILL.md +2 -1
- package/skills/workflows/REFERENCE.md +6 -4
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +664 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +1069 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +467 -0
- package/web/runtime/pi-runtime.ts +1206 -0
- package/web/runtime/types.ts +102 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1700 -0
- package/web/ui/index.html +142 -0
- package/web/ui/styles.css +680 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import {
|
|
3
|
-
|
|
5
|
+
DefaultPackageManager,
|
|
4
6
|
DefaultResourceLoader,
|
|
5
7
|
getAgentDir,
|
|
8
|
+
type LoadExtensionsResult,
|
|
9
|
+
type PackageSource,
|
|
6
10
|
ProjectTrustStore,
|
|
11
|
+
type ResolvedPaths,
|
|
7
12
|
type SessionShutdownEvent,
|
|
8
13
|
SettingsManager,
|
|
9
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import {
|
|
16
|
+
OPENPI_OWNER_SOURCE_PATHS,
|
|
17
|
+
OPENPI_TOOL_SURFACE,
|
|
18
|
+
type OpenPiToolOwner,
|
|
19
|
+
} from "./tool-surface.ts";
|
|
10
20
|
|
|
11
21
|
export const CHILD_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
12
22
|
|
|
@@ -37,16 +47,405 @@ export const CHILD_SAFE_PACKAGE_TOOL_NAMES = [
|
|
|
37
47
|
* Loading it in children can therefore cross-wire identities; the parent
|
|
38
48
|
* extension remains loaded and fully usable.
|
|
39
49
|
*/
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
function manifestNamesPiIntercom(manifestPath: string) {
|
|
51
|
+
try {
|
|
52
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as unknown;
|
|
53
|
+
return (
|
|
54
|
+
typeof manifest === "object" &&
|
|
55
|
+
manifest !== null &&
|
|
56
|
+
!Array.isArray(manifest) &&
|
|
57
|
+
(manifest as Record<string, unknown>).name === "pi-intercom"
|
|
58
|
+
);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Cannot verify child package identity from ${manifestPath}`,
|
|
62
|
+
{ cause: error },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function installedPathNamesPiIntercom(installedPath: string) {
|
|
68
|
+
try {
|
|
69
|
+
const stats = statSync(installedPath);
|
|
70
|
+
if (stats.isDirectory()) {
|
|
71
|
+
const manifestPath = path.join(installedPath, "package.json");
|
|
72
|
+
if (existsSync(manifestPath)) {
|
|
73
|
+
return manifestNamesPiIntercom(manifestPath);
|
|
74
|
+
}
|
|
75
|
+
if (path.basename(installedPath) === "pi-intercom") {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Cannot verify child package identity from ${installedPath}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (!stats.isFile()) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Cannot verify child package identity from ${installedPath}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let current = path.dirname(installedPath);
|
|
89
|
+
let hasIntercomDirectoryName = false;
|
|
90
|
+
while (true) {
|
|
91
|
+
hasIntercomDirectoryName ||= path.basename(current) === "pi-intercom";
|
|
92
|
+
const manifestPath = path.join(current, "package.json");
|
|
93
|
+
if (existsSync(manifestPath)) {
|
|
94
|
+
return manifestNamesPiIntercom(manifestPath);
|
|
95
|
+
}
|
|
96
|
+
const parent = path.dirname(current);
|
|
97
|
+
if (parent === current) {
|
|
98
|
+
if (hasIntercomDirectoryName) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Cannot verify child package identity from ${installedPath}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
current = parent;
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (
|
|
109
|
+
error instanceof Error &&
|
|
110
|
+
error.message.startsWith("Cannot verify child package identity")
|
|
111
|
+
) {
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Cannot verify child package identity from ${installedPath}`,
|
|
116
|
+
{ cause: error },
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function piMatchesPublishedIntercomSource(options: {
|
|
122
|
+
source: string;
|
|
123
|
+
cwd: string;
|
|
124
|
+
agentDir: string;
|
|
125
|
+
}) {
|
|
126
|
+
const settingsManager = SettingsManager.inMemory({
|
|
127
|
+
packages: [options.source],
|
|
128
|
+
});
|
|
129
|
+
const packageManager = new DefaultPackageManager({
|
|
130
|
+
cwd: options.cwd,
|
|
131
|
+
agentDir: options.agentDir,
|
|
132
|
+
settingsManager,
|
|
133
|
+
});
|
|
134
|
+
return (
|
|
135
|
+
packageManager.removeSourceFromSettings("npm:pi-intercom") ||
|
|
136
|
+
packageManager.removeSourceFromSettings(
|
|
137
|
+
"git:https://github.com/nicobailon/pi-intercom",
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createPiIntercomPackageMatcher(options: {
|
|
143
|
+
cwd: string;
|
|
144
|
+
agentDir: string;
|
|
145
|
+
}) {
|
|
146
|
+
const sourceMatches = new Map<string, boolean>();
|
|
147
|
+
const installedPathMatches = new Map<string, boolean>();
|
|
148
|
+
return (source: string, installedPath?: string) => {
|
|
149
|
+
let sourceMatch = sourceMatches.get(source);
|
|
150
|
+
if (sourceMatch === undefined) {
|
|
151
|
+
sourceMatch = piMatchesPublishedIntercomSource({
|
|
152
|
+
source,
|
|
153
|
+
cwd: options.cwd,
|
|
154
|
+
agentDir: options.agentDir,
|
|
155
|
+
});
|
|
156
|
+
sourceMatches.set(source, sourceMatch);
|
|
157
|
+
}
|
|
158
|
+
if (sourceMatch || installedPath === undefined) return sourceMatch;
|
|
159
|
+
|
|
160
|
+
const resolvedPath = path.resolve(installedPath);
|
|
161
|
+
let installedPathMatch = installedPathMatches.get(resolvedPath);
|
|
162
|
+
if (installedPathMatch === undefined) {
|
|
163
|
+
installedPathMatch = installedPathNamesPiIntercom(resolvedPath);
|
|
164
|
+
installedPathMatches.set(resolvedPath, installedPathMatch);
|
|
165
|
+
}
|
|
166
|
+
return installedPathMatch;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function packageSourceValue(source: PackageSource) {
|
|
171
|
+
return typeof source === "string" ? source : source.source;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const CHILD_DISABLED_OPENPI_EXTENSION =
|
|
175
|
+
"-extensions/git-info/index.ts" as const;
|
|
176
|
+
|
|
177
|
+
function canonicalExistingPath(value: string) {
|
|
178
|
+
try {
|
|
179
|
+
return realpathSync.native(value);
|
|
180
|
+
} catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* ENOENT means git-info is truly absent (trimmed fork, partial install):
|
|
187
|
+
* nothing to exclude. Any other failure (permissions, symlink loops) means we
|
|
188
|
+
* cannot verify the Git-polling extension is present, so fail closed instead
|
|
189
|
+
* of running a child with an unverifiable extension.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveGitInfoPathOrThrow(
|
|
192
|
+
resolve: (value: string) => string = realpathSync.native,
|
|
193
|
+
): string | undefined {
|
|
194
|
+
try {
|
|
195
|
+
return resolve(
|
|
196
|
+
fileURLToPath(new URL("../git-info/index.ts", import.meta.url)),
|
|
197
|
+
);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function excludeOpenPiGitInfoExtension(
|
|
205
|
+
resources: LoadExtensionsResult,
|
|
206
|
+
resolve: (value: string) => string = realpathSync.native,
|
|
207
|
+
): LoadExtensionsResult {
|
|
208
|
+
const gitInfoPath = resolveGitInfoPathOrThrow(resolve);
|
|
209
|
+
// undefined = absent (ENOENT): nothing to exclude. Non-ENOENT failures
|
|
210
|
+
// throw from resolveGitInfoPathOrThrow and fail child creation upstream.
|
|
211
|
+
if (gitInfoPath === undefined) return resources;
|
|
212
|
+
return {
|
|
213
|
+
...resources,
|
|
214
|
+
extensions: resources.extensions.filter(
|
|
215
|
+
(extension) =>
|
|
216
|
+
canonicalExistingPath(extension.resolvedPath) !== gitInfoPath,
|
|
217
|
+
),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function installedPathNamesOpenPi(installedPath: string) {
|
|
222
|
+
try {
|
|
223
|
+
const stats = statSync(installedPath);
|
|
224
|
+
if (stats.isDirectory()) {
|
|
225
|
+
const manifestPath = path.join(installedPath, "package.json");
|
|
226
|
+
if (!existsSync(manifestPath)) return false;
|
|
227
|
+
const manifest = JSON.parse(
|
|
228
|
+
readFileSync(manifestPath, "utf8"),
|
|
229
|
+
) as unknown;
|
|
230
|
+
return (
|
|
231
|
+
typeof manifest === "object" &&
|
|
232
|
+
manifest !== null &&
|
|
233
|
+
!Array.isArray(manifest) &&
|
|
234
|
+
(manifest as Record<string, unknown>).name === "@tt-a1i/openpi"
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
let current = stats.isFile() ? path.dirname(installedPath) : undefined;
|
|
238
|
+
while (current) {
|
|
239
|
+
const manifestPath = path.join(current, "package.json");
|
|
240
|
+
if (existsSync(manifestPath)) {
|
|
241
|
+
const manifest = JSON.parse(
|
|
242
|
+
readFileSync(manifestPath, "utf8"),
|
|
243
|
+
) as unknown;
|
|
244
|
+
return (
|
|
245
|
+
typeof manifest === "object" &&
|
|
246
|
+
manifest !== null &&
|
|
247
|
+
!Array.isArray(manifest) &&
|
|
248
|
+
(manifest as Record<string, unknown>).name === "@tt-a1i/openpi"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const parent = path.dirname(current);
|
|
252
|
+
if (parent === current) return false;
|
|
253
|
+
current = parent;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function piMatchesPublishedOpenPiSource(options: {
|
|
262
|
+
source: string;
|
|
263
|
+
cwd: string;
|
|
264
|
+
agentDir: string;
|
|
265
|
+
}) {
|
|
266
|
+
const settingsManager = SettingsManager.inMemory({
|
|
267
|
+
packages: [options.source],
|
|
268
|
+
});
|
|
269
|
+
const packageManager = new DefaultPackageManager({
|
|
270
|
+
cwd: options.cwd,
|
|
271
|
+
agentDir: options.agentDir,
|
|
272
|
+
settingsManager,
|
|
273
|
+
});
|
|
274
|
+
return (
|
|
275
|
+
packageManager.removeSourceFromSettings("npm:@tt-a1i/openpi") ||
|
|
276
|
+
packageManager.removeSourceFromSettings(
|
|
277
|
+
"git:https://github.com/openpi-dev/openpi",
|
|
278
|
+
) ||
|
|
279
|
+
packageManager.removeSourceFromSettings(
|
|
280
|
+
"git:https://github.com/tt-a1i/openpi",
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function createOpenPiPackageMatcher(options: {
|
|
286
|
+
cwd: string;
|
|
287
|
+
agentDir: string;
|
|
288
|
+
}) {
|
|
289
|
+
const sourceMatches = new Map<string, boolean>();
|
|
290
|
+
const installedPathMatches = new Map<string, boolean>();
|
|
291
|
+
return (source: string, installedPath?: string) => {
|
|
292
|
+
let sourceMatch = sourceMatches.get(source);
|
|
293
|
+
if (sourceMatch === undefined) {
|
|
294
|
+
sourceMatch = piMatchesPublishedOpenPiSource({ source, ...options });
|
|
295
|
+
sourceMatches.set(source, sourceMatch);
|
|
296
|
+
}
|
|
297
|
+
if (sourceMatch || installedPath === undefined) return sourceMatch;
|
|
298
|
+
|
|
299
|
+
const resolvedPath = path.resolve(installedPath);
|
|
300
|
+
let installedPathMatch = installedPathMatches.get(resolvedPath);
|
|
301
|
+
if (installedPathMatch === undefined) {
|
|
302
|
+
installedPathMatch = installedPathNamesOpenPi(resolvedPath);
|
|
303
|
+
installedPathMatches.set(resolvedPath, installedPathMatch);
|
|
304
|
+
}
|
|
305
|
+
return installedPathMatch;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function openPiPackageSources(
|
|
310
|
+
packageManager: DefaultPackageManager,
|
|
311
|
+
options: { cwd: string; agentDir: string },
|
|
312
|
+
) {
|
|
313
|
+
const isOpenPiPackage = createOpenPiPackageMatcher(options);
|
|
314
|
+
const sources = {
|
|
315
|
+
user: new Set<string>(),
|
|
316
|
+
project: new Set<string>(),
|
|
317
|
+
};
|
|
318
|
+
for (const configured of packageManager.listConfiguredPackages()) {
|
|
319
|
+
if (isOpenPiPackage(configured.source, configured.installedPath)) {
|
|
320
|
+
sources[configured.scope].add(configured.source);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return sources;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function disablesOpenPiGitInfo(pattern: string) {
|
|
327
|
+
return (
|
|
328
|
+
pattern.startsWith("-") &&
|
|
329
|
+
pattern.slice(1).replace(/^\.\//, "") ===
|
|
330
|
+
CHILD_DISABLED_OPENPI_EXTENSION.slice(1)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function disableOpenPiGitInfo(source: PackageSource): PackageSource {
|
|
335
|
+
if (typeof source !== "string") {
|
|
336
|
+
if (source.extensions?.length === 0) return source;
|
|
337
|
+
if (source.autoload === false && source.extensions === undefined) {
|
|
338
|
+
return source;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const configured = typeof source === "string" ? { source } : source;
|
|
342
|
+
const extensions = [...(configured.extensions ?? [])];
|
|
343
|
+
if (!extensions.some(disablesOpenPiGitInfo)) {
|
|
344
|
+
extensions.push(CHILD_DISABLED_OPENPI_EXTENSION);
|
|
345
|
+
}
|
|
346
|
+
return { ...configured, extensions };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function blockedPackageSources(
|
|
350
|
+
packageManager: DefaultPackageManager,
|
|
351
|
+
resolvedPaths: ResolvedPaths,
|
|
352
|
+
options: { cwd: string; agentDir: string },
|
|
353
|
+
) {
|
|
354
|
+
const isPiIntercomPackage = createPiIntercomPackageMatcher(options);
|
|
355
|
+
const blocked = {
|
|
356
|
+
user: new Set<string>(),
|
|
357
|
+
project: new Set<string>(),
|
|
358
|
+
};
|
|
359
|
+
for (const configured of packageManager.listConfiguredPackages()) {
|
|
360
|
+
if (isPiIntercomPackage(configured.source, configured.installedPath)) {
|
|
361
|
+
blocked[configured.scope].add(configured.source);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const resources = [
|
|
366
|
+
...resolvedPaths.extensions,
|
|
367
|
+
...resolvedPaths.skills,
|
|
368
|
+
...resolvedPaths.prompts,
|
|
369
|
+
...resolvedPaths.themes,
|
|
370
|
+
];
|
|
371
|
+
for (const resource of resources) {
|
|
372
|
+
const { metadata } = resource;
|
|
373
|
+
if (
|
|
374
|
+
metadata.origin !== "package" ||
|
|
375
|
+
metadata.scope === "temporary" ||
|
|
376
|
+
!isPiIntercomPackage(metadata.source, metadata.baseDir)
|
|
377
|
+
) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
blocked[metadata.scope].add(metadata.source);
|
|
381
|
+
}
|
|
382
|
+
return blocked;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function createEphemeralChildSettings(
|
|
386
|
+
sourceSettings: SettingsManager,
|
|
387
|
+
blockedSources: { user: Set<string>; project: Set<string> },
|
|
388
|
+
openPiSources: { user: Set<string>; project: Set<string> },
|
|
389
|
+
projectTrusted: boolean,
|
|
390
|
+
) {
|
|
391
|
+
const globalSettings = sourceSettings.getGlobalSettings();
|
|
392
|
+
const projectSettings = sourceSettings.getProjectSettings();
|
|
393
|
+
const settingsForScope = (
|
|
394
|
+
settings: typeof globalSettings,
|
|
395
|
+
scope: "user" | "project",
|
|
396
|
+
) => ({
|
|
397
|
+
...settings,
|
|
398
|
+
packages: settings.packages
|
|
399
|
+
?.filter(
|
|
400
|
+
(source) => !blockedSources[scope].has(packageSourceValue(source)),
|
|
401
|
+
)
|
|
402
|
+
.map((source) =>
|
|
403
|
+
openPiSources[scope].has(packageSourceValue(source))
|
|
404
|
+
? disableOpenPiGitInfo(source)
|
|
405
|
+
: source,
|
|
406
|
+
),
|
|
407
|
+
});
|
|
408
|
+
const contents = {
|
|
409
|
+
global: JSON.stringify(settingsForScope(globalSettings, "user")),
|
|
410
|
+
project: JSON.stringify(settingsForScope(projectSettings, "project")),
|
|
411
|
+
};
|
|
412
|
+
const storage: Parameters<typeof SettingsManager.fromStorage>[0] = {
|
|
413
|
+
withLock(scope, update) {
|
|
414
|
+
const next = update(contents[scope]);
|
|
415
|
+
if (next !== undefined) contents[scope] = next;
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
return SettingsManager.fromStorage(storage, { projectTrusted });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function createChildSettingsManager(options: {
|
|
422
|
+
cwd: string;
|
|
423
|
+
agentDir: string;
|
|
424
|
+
projectTrusted: boolean;
|
|
43
425
|
}) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
426
|
+
const sourceSettings = SettingsManager.create(options.cwd, options.agentDir, {
|
|
427
|
+
projectTrusted: options.projectTrusted,
|
|
428
|
+
});
|
|
429
|
+
const packageManager = new DefaultPackageManager({
|
|
430
|
+
cwd: options.cwd,
|
|
431
|
+
agentDir: options.agentDir,
|
|
432
|
+
settingsManager: sourceSettings,
|
|
433
|
+
});
|
|
434
|
+
const resolvedPaths = await packageManager.resolve(async () => "skip");
|
|
435
|
+
const blockedSources = blockedPackageSources(packageManager, resolvedPaths, {
|
|
436
|
+
cwd: options.cwd,
|
|
437
|
+
agentDir: options.agentDir,
|
|
438
|
+
});
|
|
439
|
+
const openPiSources = openPiPackageSources(packageManager, {
|
|
440
|
+
cwd: options.cwd,
|
|
441
|
+
agentDir: options.agentDir,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
return createEphemeralChildSettings(
|
|
445
|
+
sourceSettings,
|
|
446
|
+
blockedSources,
|
|
447
|
+
openPiSources,
|
|
448
|
+
options.projectTrusted,
|
|
50
449
|
);
|
|
51
450
|
}
|
|
52
451
|
|
|
@@ -96,6 +495,42 @@ export const CHILD_EXCLUDED_TOOL_NAMES = [
|
|
|
96
495
|
"context_pivot",
|
|
97
496
|
] as const;
|
|
98
497
|
|
|
498
|
+
const PARENT_ONLY_OPENPI_EXTENSION_PATHS = new Set(
|
|
499
|
+
(Object.keys(OPENPI_TOOL_SURFACE) as OpenPiToolOwner[])
|
|
500
|
+
.filter((owner) => {
|
|
501
|
+
const { entry, deferred } = OPENPI_TOOL_SURFACE[owner];
|
|
502
|
+
const toolNames = [...entry, ...deferred];
|
|
503
|
+
return (
|
|
504
|
+
toolNames.length > 0 &&
|
|
505
|
+
toolNames.every((name) =>
|
|
506
|
+
CHILD_EXCLUDED_TOOL_NAMES.includes(name as never),
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
})
|
|
510
|
+
.map((owner) => canonicalExistingPath(OPENPI_OWNER_SOURCE_PATHS[owner]))
|
|
511
|
+
.filter(
|
|
512
|
+
(extensionPath): extensionPath is string => extensionPath !== undefined,
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
function isVerifiedParentOnlyOpenPiExtension(extension: {
|
|
517
|
+
path: string;
|
|
518
|
+
resolvedPath: string;
|
|
519
|
+
sourceInfo: { path: string };
|
|
520
|
+
}) {
|
|
521
|
+
return [
|
|
522
|
+
extension.path,
|
|
523
|
+
extension.resolvedPath,
|
|
524
|
+
extension.sourceInfo.path,
|
|
525
|
+
].some((candidate) => {
|
|
526
|
+
const canonicalPath = canonicalExistingPath(candidate);
|
|
527
|
+
return (
|
|
528
|
+
canonicalPath !== undefined &&
|
|
529
|
+
PARENT_ONLY_OPENPI_EXTENSION_PATHS.has(canonicalPath)
|
|
530
|
+
);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
99
534
|
/**
|
|
100
535
|
* Fresh SDK options avoid turning the denylist into an accidental allowlist.
|
|
101
536
|
*
|
|
@@ -133,26 +568,27 @@ export interface ChildResourceOptions {
|
|
|
133
568
|
/** Load normal global/package resources and trust-gated project resources. */
|
|
134
569
|
export async function createChildResources(options: ChildResourceOptions) {
|
|
135
570
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
136
|
-
const settingsManager =
|
|
571
|
+
const settingsManager = await createChildSettingsManager({
|
|
572
|
+
cwd: options.cwd,
|
|
573
|
+
agentDir,
|
|
137
574
|
projectTrusted: options.projectTrusted,
|
|
138
575
|
});
|
|
139
576
|
const loader = new DefaultResourceLoader({
|
|
140
577
|
cwd: options.cwd,
|
|
141
578
|
agentDir,
|
|
142
579
|
settingsManager,
|
|
580
|
+
extensionsOverride(base) {
|
|
581
|
+
const withoutGitInfo = excludeOpenPiGitInfoExtension(base);
|
|
582
|
+
return {
|
|
583
|
+
...withoutGitInfo,
|
|
584
|
+
extensions: withoutGitInfo.extensions.filter(
|
|
585
|
+
(extension) => !isVerifiedParentOnlyOpenPiExtension(extension),
|
|
586
|
+
),
|
|
587
|
+
};
|
|
588
|
+
},
|
|
143
589
|
...(options.appendSystemPrompt
|
|
144
590
|
? { appendSystemPrompt: options.appendSystemPrompt }
|
|
145
591
|
: {}),
|
|
146
|
-
extensionsOverride: (base) => ({
|
|
147
|
-
...base,
|
|
148
|
-
extensions: base.extensions.filter(
|
|
149
|
-
(extension) => !isPiIntercomNpmResource(extension),
|
|
150
|
-
),
|
|
151
|
-
}),
|
|
152
|
-
skillsOverride: (base) => ({
|
|
153
|
-
...base,
|
|
154
|
-
skills: base.skills.filter((skill) => !isPiIntercomNpmResource(skill)),
|
|
155
|
-
}),
|
|
156
592
|
});
|
|
157
593
|
await loader.reload();
|
|
158
594
|
return { loader, settingsManager };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export type CompletionProducer = "subagent" | "workflow" | "background";
|
|
2
|
+
export type CompletionWakePolicy =
|
|
3
|
+
| "follow-up"
|
|
4
|
+
| "next-turn"
|
|
5
|
+
| "producer-policy";
|
|
6
|
+
|
|
7
|
+
export interface CompletionOwner {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly epoch: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CompletionSessionIdentity {
|
|
13
|
+
getSessionId(): string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Transport metadata only; producer state remains the terminal authority. */
|
|
17
|
+
export interface CompletionEnvelope<T> {
|
|
18
|
+
readonly deliveryId: string;
|
|
19
|
+
readonly owner: CompletionOwner;
|
|
20
|
+
readonly producer: CompletionProducer;
|
|
21
|
+
readonly producerId: string;
|
|
22
|
+
readonly terminalRef: unknown;
|
|
23
|
+
readonly wake: CompletionWakePolicy;
|
|
24
|
+
readonly payload: T;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CompletionDeadLetter {
|
|
28
|
+
readonly deliveryId: string;
|
|
29
|
+
readonly producer: CompletionProducer;
|
|
30
|
+
readonly producerId: string;
|
|
31
|
+
readonly failure: "owner-unavailable" | "stale-owner";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function sameOwner(left: CompletionOwner, right: CompletionOwner) {
|
|
35
|
+
return (
|
|
36
|
+
Boolean(left.sessionId) &&
|
|
37
|
+
left.sessionId !== "unowned" &&
|
|
38
|
+
left.sessionId === right.sessionId &&
|
|
39
|
+
left.epoch === right.epoch
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let nextOwnerEpoch = 1;
|
|
44
|
+
const ownerBySessionIdentity = new WeakMap<object, CompletionOwner>();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bind one process-local generation to a Pi SessionManager identity.
|
|
48
|
+
*
|
|
49
|
+
* All OpenPI producers observing the same manager share an owner. A replaced
|
|
50
|
+
* manager, or a manager whose Session id changes, receives a new epoch so late
|
|
51
|
+
* callbacks cannot target the replacement transcript.
|
|
52
|
+
*/
|
|
53
|
+
export function completionOwnerFor(
|
|
54
|
+
identity: CompletionSessionIdentity,
|
|
55
|
+
): CompletionOwner {
|
|
56
|
+
const sessionId = identity.getSessionId();
|
|
57
|
+
const existing = ownerBySessionIdentity.get(identity);
|
|
58
|
+
if (existing?.sessionId === sessionId) return existing;
|
|
59
|
+
const owner = { sessionId, epoch: nextOwnerEpoch++ };
|
|
60
|
+
ownerBySessionIdentity.set(identity, owner);
|
|
61
|
+
return owner;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One atomic consumption gate shared by all background producers.
|
|
66
|
+
*
|
|
67
|
+
* Claim removes before transport; a failed transport retries the exact
|
|
68
|
+
* envelopes. A successful transport leaves them consumed. No execution facts
|
|
69
|
+
* or result bytes are stored here.
|
|
70
|
+
*/
|
|
71
|
+
export function createCompletionInbox<T>() {
|
|
72
|
+
const pending = new Map<string, CompletionEnvelope<T>>();
|
|
73
|
+
const inFlight = new Map<string, CompletionEnvelope<T>>();
|
|
74
|
+
const deadLetters: CompletionDeadLetter[] = [];
|
|
75
|
+
|
|
76
|
+
const reject = (
|
|
77
|
+
envelope: CompletionEnvelope<T>,
|
|
78
|
+
failure: CompletionDeadLetter["failure"],
|
|
79
|
+
) => {
|
|
80
|
+
deadLetters.push({
|
|
81
|
+
deliveryId: envelope.deliveryId,
|
|
82
|
+
producer: envelope.producer,
|
|
83
|
+
producerId: envelope.producerId,
|
|
84
|
+
failure,
|
|
85
|
+
});
|
|
86
|
+
return false;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const admit = (
|
|
90
|
+
envelope: CompletionEnvelope<T>,
|
|
91
|
+
owner: CompletionOwner | undefined,
|
|
92
|
+
) => {
|
|
93
|
+
if (!owner) return reject(envelope, "owner-unavailable");
|
|
94
|
+
if (!sameOwner(envelope.owner, owner)) {
|
|
95
|
+
return reject(envelope, "stale-owner");
|
|
96
|
+
}
|
|
97
|
+
pending.set(envelope.deliveryId, envelope);
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** Restore a failed attempt ahead of completions that arrived meanwhile. */
|
|
102
|
+
const retry = (
|
|
103
|
+
envelopes: readonly CompletionEnvelope<T>[],
|
|
104
|
+
owner: CompletionOwner | undefined,
|
|
105
|
+
) => {
|
|
106
|
+
const current = [...pending.values()];
|
|
107
|
+
pending.clear();
|
|
108
|
+
for (const envelope of envelopes) {
|
|
109
|
+
inFlight.delete(envelope.deliveryId);
|
|
110
|
+
admit(envelope, owner);
|
|
111
|
+
}
|
|
112
|
+
for (const envelope of current) admit(envelope, owner);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
defer(envelope: CompletionEnvelope<T>, owner: CompletionOwner | undefined) {
|
|
117
|
+
return admit(envelope, owner);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/** Explicit status/wait and automatic delivery atomically race here. */
|
|
121
|
+
consume(producer: CompletionProducer, producerIds: Iterable<string>) {
|
|
122
|
+
const ids = new Set(producerIds);
|
|
123
|
+
for (const [deliveryId, envelope] of pending) {
|
|
124
|
+
if (envelope.producer === producer && ids.has(envelope.producerId)) {
|
|
125
|
+
pending.delete(deliveryId);
|
|
126
|
+
inFlight.delete(deliveryId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
consumeDeliveryIds(deliveryIds: Iterable<string>) {
|
|
132
|
+
for (const deliveryId of deliveryIds) {
|
|
133
|
+
pending.delete(deliveryId);
|
|
134
|
+
inFlight.delete(deliveryId);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
claim(
|
|
139
|
+
owner: CompletionOwner | undefined,
|
|
140
|
+
maximum = Number.POSITIVE_INFINITY,
|
|
141
|
+
) {
|
|
142
|
+
const claimed: CompletionEnvelope<T>[] = [];
|
|
143
|
+
for (const [deliveryId, envelope] of pending) {
|
|
144
|
+
if (claimed.length >= maximum) break;
|
|
145
|
+
pending.delete(deliveryId);
|
|
146
|
+
if (!owner) {
|
|
147
|
+
reject(envelope, "owner-unavailable");
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!sameOwner(envelope.owner, owner)) {
|
|
151
|
+
reject(envelope, "stale-owner");
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
inFlight.set(deliveryId, envelope);
|
|
155
|
+
claimed.push(envelope);
|
|
156
|
+
}
|
|
157
|
+
return claimed;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
retry,
|
|
161
|
+
|
|
162
|
+
retryClaimed(
|
|
163
|
+
producer: CompletionProducer,
|
|
164
|
+
producerIds: Iterable<string>,
|
|
165
|
+
owner: CompletionOwner | undefined,
|
|
166
|
+
) {
|
|
167
|
+
const ids = new Set(producerIds);
|
|
168
|
+
const envelopes = [...inFlight.values()].filter(
|
|
169
|
+
(envelope) =>
|
|
170
|
+
envelope.producer === producer && ids.has(envelope.producerId),
|
|
171
|
+
);
|
|
172
|
+
retry(envelopes, owner);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
acknowledge(deliveryIds: Iterable<string>) {
|
|
176
|
+
for (const deliveryId of deliveryIds) inFlight.delete(deliveryId);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
size() {
|
|
180
|
+
return pending.size;
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
inspectDeadLetters() {
|
|
184
|
+
return [...deadLetters];
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
clear() {
|
|
188
|
+
pending.clear();
|
|
189
|
+
inFlight.clear();
|
|
190
|
+
deadLetters.length = 0;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|