@tt-a1i/openpi 0.4.0 → 0.5.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.
Files changed (109) hide show
  1. package/README.md +102 -40
  2. package/SETUP.md +22 -6
  3. package/assets/openpi-launch-card-v1.webp +0 -0
  4. package/bin/openpi.js +145 -0
  5. package/extensions/background-terminals/index.ts +30 -2
  6. package/extensions/background-terminals/src/domain.ts +2 -0
  7. package/extensions/background-terminals/src/manager.ts +486 -106
  8. package/extensions/background-terminals/src/output.ts +33 -0
  9. package/extensions/background-terminals/src/prompt.ts +13 -5
  10. package/extensions/background-terminals/src/result-delivery.ts +4 -1
  11. package/extensions/clear-context/index.ts +83 -0
  12. package/extensions/context-pivot/index.ts +16 -6
  13. package/extensions/cron/schedule.ts +7 -1
  14. package/extensions/file-mutation-display/render.ts +17 -257
  15. package/extensions/file-search/src/binaries.ts +57 -41
  16. package/extensions/git-read/index.ts +1 -3
  17. package/extensions/model-info/index.ts +21 -33
  18. package/extensions/model-info/session-metrics.ts +96 -0
  19. package/extensions/plan-mode/bash-policy.ts +54 -9
  20. package/extensions/plan-mode/index.ts +7 -2
  21. package/extensions/post-edit/index.ts +16 -6
  22. package/extensions/sessions/git-stats.ts +258 -72
  23. package/extensions/sessions/index.ts +153 -86
  24. package/extensions/sessions/preview-cache.ts +104 -0
  25. package/extensions/sessions/preview-loader.ts +856 -0
  26. package/extensions/sessions/sessions.ts +43 -4
  27. package/extensions/setup/index.ts +123 -127
  28. package/extensions/shared/activity-status.ts +30 -0
  29. package/extensions/shared/agent-session-page.ts +319 -0
  30. package/extensions/shared/agent-tool-renderer.ts +218 -0
  31. package/extensions/shared/agent-transcript.ts +524 -0
  32. package/extensions/shared/capability-intent.ts +1 -1
  33. package/extensions/shared/child-session.ts +437 -21
  34. package/extensions/shared/result-delivery.ts +34 -0
  35. package/extensions/shared/setup-config.ts +73 -33
  36. package/extensions/shared/setup-episode-state.ts +1 -1
  37. package/extensions/shared/terminal-text.ts +110 -23
  38. package/extensions/shared/text-projection.ts +72 -15
  39. package/extensions/shared/tool-activity.ts +382 -0
  40. package/extensions/shared/tool-surface.ts +29 -2
  41. package/extensions/shared/transcript-viewport.ts +46 -0
  42. package/extensions/shared/web-observer-registry.ts +390 -0
  43. package/extensions/shared/worktree.ts +11 -0
  44. package/extensions/subagents/index.ts +270 -59
  45. package/extensions/subagents/navigation.ts +34 -5
  46. package/extensions/subagents/src/backend.ts +12 -1
  47. package/extensions/subagents/src/backends/pi.ts +375 -66
  48. package/extensions/subagents/src/domain.ts +5 -0
  49. package/extensions/subagents/src/manager.ts +34 -2
  50. package/extensions/subagents/src/prompt.ts +32 -4
  51. package/extensions/subagents/src/result-artifact.ts +4 -0
  52. package/extensions/subagents/src/result-delivery.ts +7 -1
  53. package/extensions/subagents/src/runtime.ts +15 -1
  54. package/extensions/subagents/src/ui/takeover.ts +73 -257
  55. package/extensions/subagents/src/ui/transcript.ts +38 -535
  56. package/extensions/subagents/src/ui/wait-result.ts +103 -15
  57. package/extensions/suggestions/src/ui.ts +10 -4
  58. package/extensions/tasks/index.ts +0 -3
  59. package/extensions/ui-customization/footer.ts +0 -40
  60. package/extensions/ui-customization/index.ts +0 -4
  61. package/extensions/user-input-fold/index.ts +1 -1
  62. package/extensions/web/index.ts +234 -0
  63. package/extensions/workflows/artifacts.ts +137 -47
  64. package/extensions/workflows/completion-projection.ts +457 -0
  65. package/extensions/workflows/coordinator.ts +8 -10
  66. package/extensions/workflows/dashboard.ts +167 -228
  67. package/extensions/workflows/handoff.ts +70 -16
  68. package/extensions/workflows/index.ts +488 -198
  69. package/extensions/workflows/journal.ts +148 -13
  70. package/extensions/workflows/model.ts +74 -4
  71. package/extensions/workflows/navigation.ts +32 -8
  72. package/extensions/workflows/progress-projection.ts +306 -0
  73. package/extensions/workflows/prompt.ts +66 -6
  74. package/extensions/workflows/replay-safety.ts +42 -21
  75. package/extensions/workflows/result-delivery.ts +128 -64
  76. package/extensions/workflows/retention.ts +593 -0
  77. package/extensions/workflows/runner.ts +388 -279
  78. package/extensions/workflows/sandbox-child.cjs +25 -3
  79. package/extensions/workflows/sandbox.ts +62 -8
  80. package/extensions/workflows/serialization.ts +325 -17
  81. package/extensions/workflows/tool-renderer.ts +22 -0
  82. package/extensions/workflows/transcript.ts +149 -0
  83. package/extensions/workspace-cleanup-guard/index.ts +54 -0
  84. package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
  85. package/package.json +28 -8
  86. package/skills/subagents/REFERENCE.md +189 -0
  87. package/skills/subagents/SKILL.md +1 -1
  88. package/skills/workflows/REFERENCE.md +4 -2
  89. package/web/adapter/pi-adapter.ts +661 -0
  90. package/web/host/browser-launcher.ts +20 -0
  91. package/web/host/static-assets.ts +4 -0
  92. package/web/host/terminal-status.ts +38 -0
  93. package/web/host/web-host.ts +789 -0
  94. package/web/http-dispatcher.ts +125 -0
  95. package/web/protocol/types.ts +462 -0
  96. package/web/runtime/pi-runtime.ts +991 -0
  97. package/web/runtime/types.ts +71 -0
  98. package/web/runtime/web-host-lease.ts +497 -0
  99. package/web/trace.ts +18 -0
  100. package/web/ui/app.js +1398 -0
  101. package/web/ui/index.html +139 -0
  102. package/web/ui/styles.css +598 -0
  103. package/web/vite.config.mjs +34 -0
  104. package/extensions/execution-convergence/active-evidence.ts +0 -129
  105. package/extensions/execution-convergence/index.ts +0 -442
  106. package/extensions/execution-convergence/workspace-provenance.ts +0 -338
  107. package/extensions/setup/intercom-fs-helper.cjs +0 -130
  108. package/extensions/setup/intercom.ts +0 -603
  109. 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
- type AgentSession,
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,385 @@ 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 isPiIntercomNpmResource(resource: {
41
- resolvedPath?: string;
42
- filePath?: string;
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;
43
125
  }) {
44
- const resourcePath = resource.resolvedPath ?? resource.filePath;
45
- if (!resourcePath) return false;
46
- const segments = path.resolve(resourcePath).split(path.sep);
47
- return segments.some(
48
- (segment, index) =>
49
- segment === "node_modules" && segments[index + 1] === "pi-intercom",
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
+ const OPENPI_GIT_INFO_EXTENSION_PATH = realpathSync.native(
177
+ fileURLToPath(new URL("../git-info/index.ts", import.meta.url)),
178
+ );
179
+
180
+ function canonicalExistingPath(value: string) {
181
+ try {
182
+ return realpathSync.native(value);
183
+ } catch {
184
+ return undefined;
185
+ }
186
+ }
187
+
188
+ function excludeOpenPiGitInfoExtension(
189
+ resources: LoadExtensionsResult,
190
+ ): LoadExtensionsResult {
191
+ return {
192
+ ...resources,
193
+ extensions: resources.extensions.filter(
194
+ (extension) =>
195
+ canonicalExistingPath(extension.resolvedPath) !==
196
+ OPENPI_GIT_INFO_EXTENSION_PATH,
197
+ ),
198
+ };
199
+ }
200
+
201
+ function installedPathNamesOpenPi(installedPath: string) {
202
+ try {
203
+ const stats = statSync(installedPath);
204
+ if (stats.isDirectory()) {
205
+ const manifestPath = path.join(installedPath, "package.json");
206
+ if (!existsSync(manifestPath)) return false;
207
+ const manifest = JSON.parse(
208
+ readFileSync(manifestPath, "utf8"),
209
+ ) as unknown;
210
+ return (
211
+ typeof manifest === "object" &&
212
+ manifest !== null &&
213
+ !Array.isArray(manifest) &&
214
+ (manifest as Record<string, unknown>).name === "@tt-a1i/openpi"
215
+ );
216
+ }
217
+ let current = stats.isFile() ? path.dirname(installedPath) : undefined;
218
+ while (current) {
219
+ const manifestPath = path.join(current, "package.json");
220
+ if (existsSync(manifestPath)) {
221
+ const manifest = JSON.parse(
222
+ readFileSync(manifestPath, "utf8"),
223
+ ) as unknown;
224
+ return (
225
+ typeof manifest === "object" &&
226
+ manifest !== null &&
227
+ !Array.isArray(manifest) &&
228
+ (manifest as Record<string, unknown>).name === "@tt-a1i/openpi"
229
+ );
230
+ }
231
+ const parent = path.dirname(current);
232
+ if (parent === current) return false;
233
+ current = parent;
234
+ }
235
+ return false;
236
+ } catch {
237
+ return false;
238
+ }
239
+ }
240
+
241
+ function piMatchesPublishedOpenPiSource(options: {
242
+ source: string;
243
+ cwd: string;
244
+ agentDir: string;
245
+ }) {
246
+ const settingsManager = SettingsManager.inMemory({
247
+ packages: [options.source],
248
+ });
249
+ const packageManager = new DefaultPackageManager({
250
+ cwd: options.cwd,
251
+ agentDir: options.agentDir,
252
+ settingsManager,
253
+ });
254
+ return (
255
+ packageManager.removeSourceFromSettings("npm:@tt-a1i/openpi") ||
256
+ packageManager.removeSourceFromSettings(
257
+ "git:https://github.com/openpi-dev/openpi",
258
+ ) ||
259
+ packageManager.removeSourceFromSettings(
260
+ "git:https://github.com/tt-a1i/openpi",
261
+ )
262
+ );
263
+ }
264
+
265
+ function createOpenPiPackageMatcher(options: {
266
+ cwd: string;
267
+ agentDir: string;
268
+ }) {
269
+ const sourceMatches = new Map<string, boolean>();
270
+ const installedPathMatches = new Map<string, boolean>();
271
+ return (source: string, installedPath?: string) => {
272
+ let sourceMatch = sourceMatches.get(source);
273
+ if (sourceMatch === undefined) {
274
+ sourceMatch = piMatchesPublishedOpenPiSource({ source, ...options });
275
+ sourceMatches.set(source, sourceMatch);
276
+ }
277
+ if (sourceMatch || installedPath === undefined) return sourceMatch;
278
+
279
+ const resolvedPath = path.resolve(installedPath);
280
+ let installedPathMatch = installedPathMatches.get(resolvedPath);
281
+ if (installedPathMatch === undefined) {
282
+ installedPathMatch = installedPathNamesOpenPi(resolvedPath);
283
+ installedPathMatches.set(resolvedPath, installedPathMatch);
284
+ }
285
+ return installedPathMatch;
286
+ };
287
+ }
288
+
289
+ function openPiPackageSources(
290
+ packageManager: DefaultPackageManager,
291
+ options: { cwd: string; agentDir: string },
292
+ ) {
293
+ const isOpenPiPackage = createOpenPiPackageMatcher(options);
294
+ const sources = {
295
+ user: new Set<string>(),
296
+ project: new Set<string>(),
297
+ };
298
+ for (const configured of packageManager.listConfiguredPackages()) {
299
+ if (isOpenPiPackage(configured.source, configured.installedPath)) {
300
+ sources[configured.scope].add(configured.source);
301
+ }
302
+ }
303
+ return sources;
304
+ }
305
+
306
+ function disablesOpenPiGitInfo(pattern: string) {
307
+ return (
308
+ pattern.startsWith("-") &&
309
+ pattern.slice(1).replace(/^\.\//, "") ===
310
+ CHILD_DISABLED_OPENPI_EXTENSION.slice(1)
311
+ );
312
+ }
313
+
314
+ function disableOpenPiGitInfo(source: PackageSource): PackageSource {
315
+ if (typeof source !== "string") {
316
+ if (source.extensions?.length === 0) return source;
317
+ if (source.autoload === false && source.extensions === undefined) {
318
+ return source;
319
+ }
320
+ }
321
+ const configured = typeof source === "string" ? { source } : source;
322
+ const extensions = [...(configured.extensions ?? [])];
323
+ if (!extensions.some(disablesOpenPiGitInfo)) {
324
+ extensions.push(CHILD_DISABLED_OPENPI_EXTENSION);
325
+ }
326
+ return { ...configured, extensions };
327
+ }
328
+
329
+ function blockedPackageSources(
330
+ packageManager: DefaultPackageManager,
331
+ resolvedPaths: ResolvedPaths,
332
+ options: { cwd: string; agentDir: string },
333
+ ) {
334
+ const isPiIntercomPackage = createPiIntercomPackageMatcher(options);
335
+ const blocked = {
336
+ user: new Set<string>(),
337
+ project: new Set<string>(),
338
+ };
339
+ for (const configured of packageManager.listConfiguredPackages()) {
340
+ if (isPiIntercomPackage(configured.source, configured.installedPath)) {
341
+ blocked[configured.scope].add(configured.source);
342
+ }
343
+ }
344
+
345
+ const resources = [
346
+ ...resolvedPaths.extensions,
347
+ ...resolvedPaths.skills,
348
+ ...resolvedPaths.prompts,
349
+ ...resolvedPaths.themes,
350
+ ];
351
+ for (const resource of resources) {
352
+ const { metadata } = resource;
353
+ if (
354
+ metadata.origin !== "package" ||
355
+ metadata.scope === "temporary" ||
356
+ !isPiIntercomPackage(metadata.source, metadata.baseDir)
357
+ ) {
358
+ continue;
359
+ }
360
+ blocked[metadata.scope].add(metadata.source);
361
+ }
362
+ return blocked;
363
+ }
364
+
365
+ function createEphemeralChildSettings(
366
+ sourceSettings: SettingsManager,
367
+ blockedSources: { user: Set<string>; project: Set<string> },
368
+ openPiSources: { user: Set<string>; project: Set<string> },
369
+ projectTrusted: boolean,
370
+ ) {
371
+ const globalSettings = sourceSettings.getGlobalSettings();
372
+ const projectSettings = sourceSettings.getProjectSettings();
373
+ const settingsForScope = (
374
+ settings: typeof globalSettings,
375
+ scope: "user" | "project",
376
+ ) => ({
377
+ ...settings,
378
+ packages: settings.packages
379
+ ?.filter(
380
+ (source) => !blockedSources[scope].has(packageSourceValue(source)),
381
+ )
382
+ .map((source) =>
383
+ openPiSources[scope].has(packageSourceValue(source))
384
+ ? disableOpenPiGitInfo(source)
385
+ : source,
386
+ ),
387
+ });
388
+ const contents = {
389
+ global: JSON.stringify(settingsForScope(globalSettings, "user")),
390
+ project: JSON.stringify(settingsForScope(projectSettings, "project")),
391
+ };
392
+ const storage: Parameters<typeof SettingsManager.fromStorage>[0] = {
393
+ withLock(scope, update) {
394
+ const next = update(contents[scope]);
395
+ if (next !== undefined) contents[scope] = next;
396
+ },
397
+ };
398
+ return SettingsManager.fromStorage(storage, { projectTrusted });
399
+ }
400
+
401
+ async function createChildSettingsManager(options: {
402
+ cwd: string;
403
+ agentDir: string;
404
+ projectTrusted: boolean;
405
+ }) {
406
+ const sourceSettings = SettingsManager.create(options.cwd, options.agentDir, {
407
+ projectTrusted: options.projectTrusted,
408
+ });
409
+ const packageManager = new DefaultPackageManager({
410
+ cwd: options.cwd,
411
+ agentDir: options.agentDir,
412
+ settingsManager: sourceSettings,
413
+ });
414
+ const resolvedPaths = await packageManager.resolve(async () => "skip");
415
+ const blockedSources = blockedPackageSources(packageManager, resolvedPaths, {
416
+ cwd: options.cwd,
417
+ agentDir: options.agentDir,
418
+ });
419
+ const openPiSources = openPiPackageSources(packageManager, {
420
+ cwd: options.cwd,
421
+ agentDir: options.agentDir,
422
+ });
423
+
424
+ return createEphemeralChildSettings(
425
+ sourceSettings,
426
+ blockedSources,
427
+ openPiSources,
428
+ options.projectTrusted,
50
429
  );
51
430
  }
52
431
 
@@ -96,6 +475,42 @@ export const CHILD_EXCLUDED_TOOL_NAMES = [
96
475
  "context_pivot",
97
476
  ] as const;
98
477
 
478
+ const PARENT_ONLY_OPENPI_EXTENSION_PATHS = new Set(
479
+ (Object.keys(OPENPI_TOOL_SURFACE) as OpenPiToolOwner[])
480
+ .filter((owner) => {
481
+ const { entry, deferred } = OPENPI_TOOL_SURFACE[owner];
482
+ const toolNames = [...entry, ...deferred];
483
+ return (
484
+ toolNames.length > 0 &&
485
+ toolNames.every((name) =>
486
+ CHILD_EXCLUDED_TOOL_NAMES.includes(name as never),
487
+ )
488
+ );
489
+ })
490
+ .map((owner) => canonicalExistingPath(OPENPI_OWNER_SOURCE_PATHS[owner]))
491
+ .filter(
492
+ (extensionPath): extensionPath is string => extensionPath !== undefined,
493
+ ),
494
+ );
495
+
496
+ function isVerifiedParentOnlyOpenPiExtension(extension: {
497
+ path: string;
498
+ resolvedPath: string;
499
+ sourceInfo: { path: string };
500
+ }) {
501
+ return [
502
+ extension.path,
503
+ extension.resolvedPath,
504
+ extension.sourceInfo.path,
505
+ ].some((candidate) => {
506
+ const canonicalPath = canonicalExistingPath(candidate);
507
+ return (
508
+ canonicalPath !== undefined &&
509
+ PARENT_ONLY_OPENPI_EXTENSION_PATHS.has(canonicalPath)
510
+ );
511
+ });
512
+ }
513
+
99
514
  /**
100
515
  * Fresh SDK options avoid turning the denylist into an accidental allowlist.
101
516
  *
@@ -133,26 +548,27 @@ export interface ChildResourceOptions {
133
548
  /** Load normal global/package resources and trust-gated project resources. */
134
549
  export async function createChildResources(options: ChildResourceOptions) {
135
550
  const agentDir = options.agentDir ?? getAgentDir();
136
- const settingsManager = SettingsManager.create(options.cwd, agentDir, {
551
+ const settingsManager = await createChildSettingsManager({
552
+ cwd: options.cwd,
553
+ agentDir,
137
554
  projectTrusted: options.projectTrusted,
138
555
  });
139
556
  const loader = new DefaultResourceLoader({
140
557
  cwd: options.cwd,
141
558
  agentDir,
142
559
  settingsManager,
560
+ extensionsOverride(base) {
561
+ const withoutGitInfo = excludeOpenPiGitInfoExtension(base);
562
+ return {
563
+ ...withoutGitInfo,
564
+ extensions: withoutGitInfo.extensions.filter(
565
+ (extension) => !isVerifiedParentOnlyOpenPiExtension(extension),
566
+ ),
567
+ };
568
+ },
143
569
  ...(options.appendSystemPrompt
144
570
  ? { appendSystemPrompt: options.appendSystemPrompt }
145
571
  : {}),
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
572
  });
157
573
  await loader.reload();
158
574
  return { loader, settingsManager };
@@ -0,0 +1,34 @@
1
+ /** Minimum lifecycle contract shared by deferred result queues. */
2
+ export interface ResultDeliveryQueue<T> {
3
+ defer(result: T): void;
4
+ size(): number;
5
+ clear(): void;
6
+ }
7
+
8
+ /** One-shot results are removed by either an explicit consumer or delivery. */
9
+ export interface ConsumableResultDeliveryQueue<T, TId = string>
10
+ extends ResultDeliveryQueue<T> {
11
+ consume(ids: Iterable<TId>): void;
12
+ }
13
+
14
+ /** Stable identity carried by a durable, at-least-once result. */
15
+ export interface DurableResultEnvelope {
16
+ deliveryId: string;
17
+ }
18
+
19
+ /** Per-result acknowledgement for a durable delivery attempt. */
20
+ export interface DurableResultDeliveryReceipt {
21
+ deliveryId: string;
22
+ delivered: boolean;
23
+ error?: string;
24
+ }
25
+
26
+ /**
27
+ * Durable queues can reconstruct pending ownership after restart and retry it
28
+ * at an explicit lifecycle boundary without changing the stable delivery id.
29
+ */
30
+ export interface DurableResultDeliveryQueue<T extends DurableResultEnvelope>
31
+ extends ResultDeliveryQueue<T> {
32
+ restore(result: T): boolean;
33
+ retryPending(): Promise<void>;
34
+ }