@opengeni/runtime 3.0.1 → 4.0.0-canary.35585322809001

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 (99) hide show
  1. package/dist/assets/codemode-client.json +1 -0
  2. package/dist/chunk-4A4XTKKX.js +1 -0
  3. package/dist/{chunk-RIJ6DCYS.js → chunk-HCGLD7NH.js} +9817 -9494
  4. package/dist/chunk-HCGLD7NH.js.map +1 -0
  5. package/dist/{chunk-FHGLYLBL.js → chunk-SPT3PLMX.js} +1 -87
  6. package/dist/chunk-SPT3PLMX.js.map +1 -0
  7. package/dist/{chunk-GB763TSV.js → chunk-TE27J65Q.js} +1 -1
  8. package/dist/chunk-TE27J65Q.js.map +1 -0
  9. package/dist/{chunk-46BFUGFL.js → chunk-WIHICNWS.js} +1660 -388
  10. package/dist/chunk-WIHICNWS.js.map +1 -0
  11. package/dist/{chunk-UU6EJR52.js → chunk-YU4VRTR4.js} +2 -2
  12. package/dist/chunk-YU4VRTR4.js.map +1 -0
  13. package/dist/chunk-ZG2YH25H.js +94 -0
  14. package/dist/chunk-ZG2YH25H.js.map +1 -0
  15. package/dist/codex-tool-search.d.ts +5 -4
  16. package/dist/context-compaction.d.ts +19 -0
  17. package/dist/gmail-rest-mcp.js +1 -1
  18. package/dist/index.d.ts +30 -22
  19. package/dist/index.js +25 -5
  20. package/dist/lazy-manifest-OEYEHNVY.js +45 -0
  21. package/dist/lazy-manifest-OEYEHNVY.js.map +1 -0
  22. package/dist/lazy-manifest.d.ts +6 -0
  23. package/dist/lazy-tool-transport.d.ts +3 -2
  24. package/dist/{localSources-PVFCAPN2.js → localSources-TWHTEKGO.js} +3 -2
  25. package/dist/localSources-TWHTEKGO.js.map +1 -0
  26. package/dist/metrics.d.ts +6 -0
  27. package/dist/operational-instructions.d.ts +1 -1
  28. package/dist/prepared-compaction-request.d.ts +8 -0
  29. package/dist/provider-history-adapter.d.ts +2 -2
  30. package/dist/runtime-skills.d.ts +3 -10
  31. package/dist/sandbox/codemode-client.d.ts +17 -0
  32. package/dist/sandbox/codemode-token.d.ts +3 -3
  33. package/dist/sandbox/index.d.ts +2 -1
  34. package/dist/sandbox/index.js +8 -2
  35. package/dist/sandbox/materialization-verification-error.d.ts +27 -0
  36. package/dist/sandbox/provider-command-session.d.ts +44 -2
  37. package/dist/sandbox/providers/modal-command-control.d.ts +32 -29
  38. package/dist/sandbox/providers/modal-command-router-wire.d.ts +49 -0
  39. package/dist/sandbox/providers/modal-command-session.d.ts +2 -1
  40. package/dist/sandbox/providers/modal-legacy-command-control.d.ts +50 -0
  41. package/dist/sandbox/providers/modal-materialization-verification.d.ts +7 -0
  42. package/dist/sandbox/providers/modal.d.ts +0 -8
  43. package/dist/sandbox/routing/routing-session.d.ts +21 -0
  44. package/dist/sandbox/turn-tool-cancellation.d.ts +1 -0
  45. package/dist/skill-library.d.ts +0 -6
  46. package/dist/skill-library.js +1 -1
  47. package/dist/workspace-tool-gateway.js +7 -5
  48. package/package.json +13 -11
  49. package/src/bundled_artifact_skills/opengeni-documents/SKILL.md +19 -0
  50. package/src/bundled_artifact_skills/opengeni-documents/references/api.md +22 -0
  51. package/src/bundled_default_skills/opengeni-client/SKILL.md +349 -0
  52. package/src/bundled_default_skills/opengeni-client/agents/openai.yaml +4 -0
  53. package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +256 -0
  54. package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +84 -0
  55. package/src/bundled_default_skills/opengeni-client/references/customer-skill-template.md +96 -0
  56. package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +99 -0
  57. package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +81 -0
  58. package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +457 -0
  59. package/src/bundled_default_skills/opengeni-client/references/isolation-and-authorization.md +77 -0
  60. package/src/bundled_default_skills/opengeni-client/references/product-integration-shapes.md +232 -0
  61. package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +116 -0
  62. package/src/bundled_default_skills/opengeni-client/references/runtime-profile-and-verification.md +157 -0
  63. package/src/bundled_default_skills/opengeni-help/SKILL.md +101 -0
  64. package/src/bundled_management_skills/opengeni-skills/SKILL.md +33 -3
  65. package/src/codex-tool-search.ts +13 -5
  66. package/src/context-compaction.ts +87 -4
  67. package/src/gmail-rest-mcp.ts +1 -2
  68. package/src/index.ts +363 -131
  69. package/src/lazy-manifest.ts +55 -0
  70. package/src/lazy-tool-transport.ts +15 -2
  71. package/src/metrics.ts +6 -0
  72. package/src/model-request-capture.ts +3 -0
  73. package/src/operational-instructions.ts +11 -2
  74. package/src/prepared-compaction-request.ts +51 -0
  75. package/src/provider-history-adapter.ts +12 -3
  76. package/src/runtime-skills.ts +3 -17
  77. package/src/sandbox/codemode-client.ts +204 -0
  78. package/src/sandbox/codemode-token.ts +7 -2
  79. package/src/sandbox/index.ts +7 -0
  80. package/src/sandbox/materialization-verification-error.ts +65 -0
  81. package/src/sandbox/provider-command-session.ts +70 -2
  82. package/src/sandbox/providers/modal-command-control.ts +386 -242
  83. package/src/sandbox/providers/modal-command-router-wire.ts +248 -0
  84. package/src/sandbox/providers/modal-command-session.ts +177 -4
  85. package/src/sandbox/providers/modal-legacy-command-control.ts +351 -0
  86. package/src/sandbox/providers/modal-materialization-verification.ts +122 -0
  87. package/src/sandbox/providers/modal.ts +1 -9
  88. package/src/sandbox/routing/routing-session.ts +417 -79
  89. package/src/sandbox/turn-tool-cancellation.ts +38 -2
  90. package/src/skill-catalog.ts +1 -0
  91. package/src/skill-library.ts +0 -6
  92. package/src/workspace-governance.ts +0 -1
  93. package/src/workspace-skills.ts +114 -74
  94. package/dist/chunk-46BFUGFL.js.map +0 -1
  95. package/dist/chunk-FHGLYLBL.js.map +0 -1
  96. package/dist/chunk-GB763TSV.js.map +0 -1
  97. package/dist/chunk-RIJ6DCYS.js.map +0 -1
  98. package/dist/chunk-UU6EJR52.js.map +0 -1
  99. /package/dist/{localSources-PVFCAPN2.js.map → chunk-4A4XTKKX.js.map} +0 -0
@@ -0,0 +1,55 @@
1
+ import { Manifest, type SandboxSessionLike } from "@openai/agents/sandbox";
2
+ import { isDeepStrictEqual } from "node:util";
3
+
4
+ /** Record already-materialized directory placeholders after lazy setup. This is
5
+ * not manifest application: never change environment, permissions, grants,
6
+ * mounts, files, or existing entries. Non-placeholder deltas remain visible to
7
+ * the SDK's normal validation/invalidation path. */
8
+ export async function recordLazyMaterializedDirectories(
9
+ session: SandboxSessionLike,
10
+ target: Manifest,
11
+ ): Promise<void> {
12
+ const current = session.state.manifest;
13
+ const { entries: currentEntries, ...currentMetadata } = current;
14
+ const { entries: targetEntries, ...targetMetadata } = target;
15
+ if (!isDeepStrictEqual(currentMetadata, targetMetadata) || !session.listDir) return;
16
+ const additions = Object.entries(targetEntries).filter(([path]) => !(path in currentEntries));
17
+ if (
18
+ Object.entries(currentEntries).some(
19
+ ([path, entry]) => path in targetEntries && !isDeepStrictEqual(entry, targetEntries[path]),
20
+ )
21
+ )
22
+ return;
23
+ // A plain directory is the logical placeholder for a repository cloned by
24
+ // setup. Rich directories, sources and permissions require SDK materialization.
25
+ const emptyDirectory = new Manifest({ entries: { placeholder: { type: "dir" } } }).entries
26
+ .placeholder;
27
+ if (additions.some(([, entry]) => !isDeepStrictEqual(entry, emptyDirectory))) return;
28
+ // iterEntries validates every logical path before any live filesystem access.
29
+ const paths = new Map([...target.iterEntries()].map((entry) => [entry.logicalPath, entry]));
30
+ const directories = new Map<
31
+ string,
32
+ Awaited<ReturnType<NonNullable<SandboxSessionLike["listDir"]>>>
33
+ >();
34
+ for (const [path] of additions) {
35
+ let directory = target.root;
36
+ // Listing the target itself follows symlinks on some adapters. Walk every
37
+ // ancestor through its parent's typed listing instead, exactly like the
38
+ // repository reader. Cache only these invocation-local parent listings.
39
+ for (const name of paths.get(path)!.logicalPath.split("/")) {
40
+ let entries = directories.get(directory);
41
+ if (!entries) {
42
+ entries = await session.listDir({ path: directory });
43
+ directories.set(directory, entries);
44
+ }
45
+ if (!entries.some((entry) => entry.name === name && entry.type === "dir")) return;
46
+ directory = `${directory.replace(/\/$/, "")}/${name}`;
47
+ }
48
+ }
49
+ // Do not overwrite a concurrent manifest update.
50
+ if (session.state.manifest !== current || additions.length === 0) return;
51
+ session.state.manifest = new Manifest({
52
+ ...current,
53
+ entries: { ...currentEntries, ...targetEntries },
54
+ });
55
+ }
@@ -1,3 +1,4 @@
1
+ import { rememberPreparedModelRequest } from "./prepared-compaction-request";
1
2
  import {
2
3
  tool as agentTool,
3
4
  toolSearchTool,
@@ -185,6 +186,7 @@ export class LazyToolRuntime {
185
186
  private readonly toolPreparationReady?: Promise<void>,
186
187
  private readonly deferredMcpServerIds: ReadonlySet<string> = mcpServerIds,
187
188
  private readonly preparationIndependentToolNames: ReadonlySet<string> = new Set(),
189
+ private readonly modelMcpServerIds: () => ReadonlyMap<string, string> = () => new Map(),
188
190
  ) {
189
191
  this.controlTools =
190
192
  transport !== "generic_dispatch"
@@ -289,6 +291,7 @@ export class LazyToolRuntime {
289
291
  this.currentTools = tools;
290
292
  this.functionTools.clear();
291
293
  this.searchableToolNames.clear();
294
+ const modelServerIds = this.modelMcpServerIds();
292
295
  for (const tool of tools) {
293
296
  if (!isFunctionTool(tool)) continue;
294
297
  this.functionTools.set(tool.name, tool);
@@ -297,8 +300,8 @@ export class LazyToolRuntime {
297
300
  // Origin, not transport: deferred MCP plus every non-MCP function tool
298
301
  // outside the base set. ToolRef.eager still decides the MCP arm.
299
302
  const lazy =
300
- isSearchableMcpFunctionTool(tool, this.deferredMcpServerIds) ||
301
- !isSearchableMcpFunctionTool(tool, this.mcpServerIds);
303
+ isSearchableMcpFunctionTool(tool, this.deferredMcpServerIds, modelServerIds) ||
304
+ !isSearchableMcpFunctionTool(tool, this.mcpServerIds, modelServerIds);
302
305
  if (lazy) {
303
306
  // After the preparation fence, deferred tools stay off the Agent
304
307
  // list. Search teaches names; a remembered raw name binds later
@@ -489,6 +492,12 @@ export class LazyToolRuntime {
489
492
  tools: descriptors,
490
493
  total: tools.length,
491
494
  nextCursor: index < tools.length ? descriptors.at(-1)!.name : null,
495
+ ...(prefix && tools.length === 0
496
+ ? {
497
+ message:
498
+ "No tool names match this literal prefix. Retry tool_list without namePrefix to browse the authorized catalog; tool names can include a server namespace.",
499
+ }
500
+ : {}),
492
501
  });
493
502
  },
494
503
  }) as unknown as Tool;
@@ -581,6 +590,7 @@ export function installLazyToolRuntime(
581
590
  toolPreparationReady?: Promise<void>,
582
591
  deferredMcpServerIds: ReadonlySet<string> = mcpServerIds,
583
592
  preparationIndependentToolNames: ReadonlySet<string> = new Set(),
593
+ modelMcpServerIds?: () => ReadonlyMap<string, string>,
584
594
  ): LazyToolRuntime {
585
595
  const runtime = new LazyToolRuntime(
586
596
  transport,
@@ -588,6 +598,7 @@ export function installLazyToolRuntime(
588
598
  toolPreparationReady,
589
599
  deferredMcpServerIds,
590
600
  preparationIndependentToolNames,
601
+ modelMcpServerIds,
591
602
  );
592
603
  installLazyToolRuntimeOnAgent(agent, runtime);
593
604
  return runtime;
@@ -815,6 +826,7 @@ class LazyToolModel implements Model {
815
826
 
816
827
  async getResponse(request: ModelRequest): Promise<ModelResponse> {
817
828
  const prepared = prepareLazyToolRequest(request, this.runtime);
829
+ rememberPreparedModelRequest(prepared);
818
830
  void notifyModelRequestCapture(prepared);
819
831
  const response = await this.inner.getResponse(prepared);
820
832
  if (responseRequiresToolPreparation(response, this.runtime)) {
@@ -827,6 +839,7 @@ class LazyToolModel implements Model {
827
839
 
828
840
  async *getStreamedResponse(request: ModelRequest): AsyncIterable<StreamEvent> {
829
841
  const prepared = prepareLazyToolRequest(request, this.runtime);
842
+ rememberPreparedModelRequest(prepared);
830
843
  void notifyModelRequestCapture(prepared);
831
844
  for await (const event of this.inner.getStreamedResponse(prepared)) {
832
845
  if (event.type === "response_done") {
package/src/metrics.ts CHANGED
@@ -48,6 +48,12 @@ export type RuntimeMetricsHooks = {
48
48
  outcome: "put" | "put_failed" | "deleted_unpublished";
49
49
  backend: string;
50
50
  }) => void;
51
+ /** Physical capture plus publication; emitted at settlement, not caller timeout. */
52
+ onWorkspaceCapture?: (input: {
53
+ backend: string;
54
+ outcome: "completed" | "failed";
55
+ durationSeconds: number;
56
+ }) => void;
51
57
  /**
52
58
  * One physical MCP tools/call invocation. The closed outcome enum deliberately
53
59
  * excludes server, tool, tenant, request, and error-content labels.
@@ -1,3 +1,4 @@
1
+ import { rememberPreparedModelRequest } from "./prepared-compaction-request";
1
2
  import { AsyncLocalStorage } from "node:async_hooks";
2
3
  import type { Model, ModelProvider, ModelRequest, StreamEvent } from "@openai/agents";
3
4
 
@@ -132,11 +133,13 @@ export class ModelRequestCaptureModel implements Model {
132
133
  constructor(private readonly inner: Model) {}
133
134
 
134
135
  async getResponse(request: ModelRequest) {
136
+ rememberPreparedModelRequest(request);
135
137
  void notifyModelRequestCapture(request);
136
138
  return this.inner.getResponse(request);
137
139
  }
138
140
 
139
141
  async *getStreamedResponse(request: ModelRequest): AsyncIterable<StreamEvent> {
142
+ rememberPreparedModelRequest(request);
140
143
  void notifyModelRequestCapture(request);
141
144
  yield* this.inner.getStreamedResponse(request);
142
145
  }
@@ -60,9 +60,12 @@ In your final answer back to the user, focus on the most important information.
60
60
  Your answer is being rendered by an application for the user. Follow these guidelines to make sure your answer is rendered correctly:
61
61
 
62
62
  - You may format with GitHub-flavored Markdown.
63
- - When referencing a real local file, prefer a clickable markdown link.
63
+ - User-facing reports are durable document Artifacts by default, including audit or summary reports produced while doing another task. Read the opengeni-documents Skill and create the native document artifact before authoring the report. Do not write a sandbox Markdown/DOCX report first or treat publishing a file as native document creation.
64
+ - Declare report deliverables through the available goal tools before authoring, including reports discovered after a goal was created. Inspect the relevant final artifact head after the last edit, supply its verified delivery evidence at goal completion, and give the user the artifact reference returned by the tools. A sandbox path, a raw file ID, or an assertion that a report exists is not completed report delivery.
65
+ - If artifact creation, inspection, access, or delivery tooling is unavailable or fails, report the concrete blocker and leave report delivery incomplete. Do not silently fall back to a sandbox link, invent an artifact reference, or claim success. Ordinary in-chat answers, brief progress updates, internal worker findings, source-code navigation, and explicitly requested local-file workflows do not become report deliverables merely because they contain Markdown or a file link.
66
+ - When referencing a real local source file or an explicitly requested local file, prefer a clickable markdown link.
64
67
  * Clickable file links should look like [app.py](sandbox:/workspace/app.py:12): plain label, sandbox:/workspace/... target, with optional line number after the path.
65
- * If a file path has spaces, wrap the target in angle brackets: [My Report.md](<sandbox:/workspace/My Project/My Report.md:3>).
68
+ * If a file path has spaces, wrap the target in angle brackets: [My Component.ts](<sandbox:/workspace/My Project/My Component.ts:3>).
66
69
  * Use the active workspace path exactly as exposed to you. Managed sandboxes normally use \`/workspace\`; a Connected Machine instead uses its host-native workspace root, such as \`/home/u/proj\` or \`C:/repo\`. Both are valid inside a \`sandbox:\` link when they are the active workspace.
67
70
  * Connected Machine examples are [app.py](sandbox:/home/u/proj/app.py:12) on POSIX and [app.ts](<sandbox:C:/repo/app.ts:12>) on Windows.
68
71
  * On a Connected Machine, absolute file links may point outside the working directory (including sibling worktrees and temporary files); use the real path on the selected machine.
@@ -147,12 +150,18 @@ Skills are reusable instructions supplied dynamically for the current session. W
147
150
  - The user's instructions take precedence over skill guidance.
148
151
  - If a named skill is unavailable or cannot be read, say so briefly and continue with the best fallback.
149
152
 
153
+ # Integration setup
154
+
155
+ Use available integration tools directly. If the task needs an integration you cannot access, discover it with \`capability_catalog_search\` before declaring a setup blocker. For a suitable match with \`setup.nextAction\`, call \`capability_authorization_request\` with the returned capability ID and a brief task-specific rationale to show its Connect card in chat. Requesting the card does not need integration-management permission; the authenticated human must authorize setup. After setup, rediscover the tools and continue the task, verifying the access it needs. If access remains blocked, explain the specific blocker from the returned facts. If either setup tool is unavailable, report the missing setup path.
156
+
150
157
  # Session coordination
151
158
 
152
159
  Use \`session_events\` for conversation history: its default returns user and completed assistant messages, not execution noise. Cursors only paginate. Request \`results\` for final outcomes, \`tools\` for tool receipts, or \`debug\` for explicit diagnostics; request large tool bodies only when needed. Use the returned continuation cursor rather than rereading whole pages. Audit reads do not acknowledge command completion.
153
160
 
154
161
  For a yielded command, use \`command_read\` to read available output and status, or \`command_wait\` to wait briefly using the same command interface. Keep the command ID and output cursor. A terminal read suppresses any still-pending completion notification; a running read does not. Earlier tool results and delivered messages never change. Use \`command_input\` only to send input where supported, not to poll output. An unsupported input capability does not imply output is unavailable. Give foreground commands a realistic requested wait; default to 10 seconds (yield_time_ms: 10000). An internal polling slice is not a reason to return a background handle.
155
162
 
163
+ Command completion alone resumes you only while you have an explicit \`wait_for_input\` registered. If remaining work depends on background commands, register that session-level wait before ending your turn. If you finish normally, command results remain retained and can accompany later input, but do not start another turn by themselves. No per-command dismissal is required.
164
+
156
165
  If the user asks to create, inspect, continue, pause, resume, steer, rename, or otherwise manage a session, use the corresponding session tool. Pause affects the selected workstream and its descendants: pausing an ancestor also stops you, so you cannot then Resume yourself. Coordinate disjoint edits through messages instead of ancestor Pause.
157
166
 
158
167
  Create a child worker only for a concrete, bounded subtask that can run independently and whose result has a clear integration point in the current request. Before spawning, decide what output you need and keep the parent's concurrent work disjoint. Do not delegate a scope that you will also perform yourself. If no useful independent work remains, continue in this session. Do not repurpose or direct an unrelated existing session unless the user explicitly asks. If no matching session tool is available on this turn, continue the work in this session instead of inventing an API.
@@ -0,0 +1,51 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { CallModelInputFilter, ModelRequest } from "@openai/agents";
3
+ import { findCompactionNeededError } from "./context-compaction";
4
+
5
+ /** One owner per run; never share a prefix between sessions or rebuilt agents. */
6
+ const requests = new WeakMap<object, Omit<ModelRequest, "input">>();
7
+ const dispatch = new AsyncLocalStorage<{ agent: object; pending?: unknown }>();
8
+ const queued = new WeakMap<object, unknown>();
9
+
10
+ export function queuePreparedCompaction(agent: object, error: unknown): void {
11
+ queued.set(agent, error);
12
+ }
13
+
14
+ export function withPreparedCompactionRequest<T>(agent: object, run: () => T): T {
15
+ const pending = queued.get(agent);
16
+ queued.delete(agent);
17
+ return dispatch.run({ agent, pending }, run);
18
+ }
19
+
20
+ /** Called after SDK sandbox preparation AND lazy-tool filtering, before transport. */
21
+ export function rememberPreparedModelRequest(request: ModelRequest): void {
22
+ const scope = dispatch.getStore();
23
+ if (!scope) return;
24
+ const { input: _input, ...prefix } = request;
25
+ requests.set(scope.agent, {
26
+ ...prefix,
27
+ tools: structuredClone(request.tools),
28
+ modelSettings: structuredClone(request.modelSettings),
29
+ });
30
+ if (scope.pending) throw scope.pending;
31
+ }
32
+
33
+ export function preparedCompactionRequest(agent: object): Omit<ModelRequest, "input"> {
34
+ const request = requests.get(agent);
35
+ if (!request) throw new Error("Compaction requires a prepared model request");
36
+ return request;
37
+ }
38
+
39
+ /** Finish normal preparation, but never send ordinary inference once compaction is due. */
40
+ export function deferCompactionToModelBoundary(filter: CallModelInputFilter): CallModelInputFilter {
41
+ return async (args) => {
42
+ try {
43
+ return await filter(args);
44
+ } catch (error) {
45
+ const scope = dispatch.getStore();
46
+ if (!scope || !findCompactionNeededError(error)) throw error;
47
+ scope.pending = error;
48
+ return args.modelData;
49
+ }
50
+ };
51
+ }
@@ -73,15 +73,24 @@ function isChatIncompatibleHostedToolCall(item: Record<string, unknown>): boolea
73
73
 
74
74
  /**
75
75
  * Build the one attempt-local history view required by the target wire API.
76
- * Canonical history remains untouched. Responses history is returned by
77
- * reference. Chat-compatible history is also returned by reference when every
76
+ * Canonical history remains untouched. SDK-unsupported developer messages use
77
+ * the Responses pass-through item. Other history is returned by reference when every
78
78
  * item is already representable by the SDK's Chat Completions converter.
79
79
  */
80
80
  export function projectHistoryForProvider(
81
81
  items: Array<Record<string, unknown>>,
82
82
  providerApi: HistoryProviderApi,
83
83
  ): Array<Record<string, unknown>> {
84
- if (providerApi === "responses") return items;
84
+ if (providerApi === "responses") {
85
+ if (!items.some((item) => item.type === "message" && item.role === "developer")) return items;
86
+ // agents-js 0.14's message converter supports system/user/assistant only.
87
+ // The Responses API itself supports developer; use the SDK's raw-item adapter.
88
+ return items.map((item) =>
89
+ item.type === "message" && item.role === "developer"
90
+ ? { type: "unknown", providerData: item }
91
+ : item,
92
+ );
93
+ }
85
94
 
86
95
  const incompatibleCallIds = new Set<string>();
87
96
  for (const item of items) {
@@ -19,8 +19,7 @@ export type RuntimeSkillArtifactFile = Readonly<{
19
19
 
20
20
  /**
21
21
  * One immutable Skill artifact that may be activated by an installation, a
22
- * Pack, or an exact session selection. The artifact itself is deliberately
23
- * acquisition-neutral: Packs do not own a second Skill representation.
22
+ * session selection. The artifact itself is acquisition-neutral.
24
23
  */
25
24
  export type RuntimeSkillArtifact = Readonly<{
26
25
  name: string;
@@ -37,13 +36,6 @@ export type InstalledSkillActivation = Readonly<{
37
36
  reason: string;
38
37
  }>;
39
38
 
40
- export type PackSkillActivation = Readonly<{
41
- source: "pack";
42
- id: string;
43
- artifact: RuntimeSkillArtifact;
44
- reason: string;
45
- }>;
46
-
47
39
  export type SessionSkillActivation = Readonly<{
48
40
  source: "session";
49
41
  id: string;
@@ -51,10 +43,7 @@ export type SessionSkillActivation = Readonly<{
51
43
  reason: string;
52
44
  }>;
53
45
 
54
- export type RuntimeSkillActivation =
55
- | InstalledSkillActivation
56
- | PackSkillActivation
57
- | SessionSkillActivation;
46
+ export type RuntimeSkillActivation = InstalledSkillActivation | SessionSkillActivation;
58
47
 
59
48
  export type NativeToolSkillSet = Readonly<{
60
49
  /** False when an embedding host owns the complete Skill catalog and reader. */
@@ -67,7 +56,7 @@ export type NativeToolSkillSet = Readonly<{
67
56
  export type EffectiveSkillSelection = Readonly<{
68
57
  id: string;
69
58
  name: string;
70
- source: "installation" | "pack" | "session" | "native_tool";
59
+ source: "installation" | "session" | "native_tool";
71
60
  version: string | null;
72
61
  contentSha256: string | null;
73
62
  reason: string;
@@ -321,8 +310,6 @@ function activationPrecedence(source: RuntimeSkillActivation["source"]): number
321
310
  switch (source) {
322
311
  case "installation":
323
312
  return 1;
324
- case "pack":
325
- return 2;
326
313
  case "session":
327
314
  return 3;
328
315
  }
@@ -369,7 +356,6 @@ function selectionForActivation({
369
356
  contentSha256,
370
357
  reason: activation.reason,
371
358
  });
372
- case "pack":
373
359
  case "session":
374
360
  return Object.freeze({
375
361
  id: activation.id,
@@ -0,0 +1,204 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join, posix, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import type { SandboxSessionLike } from "@openai/agents/sandbox";
6
+ import { sandboxCommandExitCode } from "./command-result";
7
+
8
+ /** Data shipped with the worker, never fetched from a package registry at turn time. */
9
+ export type ManagedCodemodeClient = {
10
+ version: 1;
11
+ files: { ogtool: string; "client.mjs": string; "package.json": string };
12
+ };
13
+
14
+ export function managedCodemodeClientDigest(client: ManagedCodemodeClient): string {
15
+ return createHash("sha256").update(JSON.stringify(client)).digest("hex");
16
+ }
17
+
18
+ export function managedCodemodeClientDirectory(
19
+ client: ManagedCodemodeClient,
20
+ workspaceRoot = "/workspace",
21
+ ): string {
22
+ if (!posix.isAbsolute(workspaceRoot) || workspaceRoot.includes("\0")) {
23
+ throw new Error("Managed Codemode client workspace root must be absolute");
24
+ }
25
+ return posix.join(
26
+ workspaceRoot,
27
+ ".opengeni/codemode-clients",
28
+ managedCodemodeClientDigest(client),
29
+ );
30
+ }
31
+
32
+ export async function buildManagedCodemodeClient(root: string): Promise<ManagedCodemodeClient> {
33
+ const bundle = async (entry: string, format: "esm" | "cjs", banner?: string) => {
34
+ const result = await Bun.build({
35
+ entrypoints: [join(root, entry)],
36
+ target: "node",
37
+ format,
38
+ minify: true,
39
+ sourcemap: "none",
40
+ ...(banner ? { banner } : {}),
41
+ });
42
+ if (!result.success || result.outputs.length !== 1) {
43
+ throw new Error(`Managed Codemode client build failed: ${entry}`);
44
+ }
45
+ return await result.outputs[0]!.text();
46
+ };
47
+ return {
48
+ version: 1,
49
+ files: {
50
+ ogtool: await bundle("packages/ogtool/src/cli.ts", "cjs", "#!/usr/bin/env node"),
51
+ "client.mjs": await bundle("packages/codemode/src/index.ts", "esm"),
52
+ // The extensionless executable must not inherit a user's ESM workspace.
53
+ "package.json": '{"type":"commonjs"}\n',
54
+ },
55
+ };
56
+ }
57
+
58
+ let releaseClient: Promise<ManagedCodemodeClient> | undefined;
59
+ export function loadManagedCodemodeClient(): Promise<ManagedCodemodeClient> {
60
+ return (releaseClient ??= (async () => {
61
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
62
+ // A checkout must not reuse stale dist assets after source changes.
63
+ const root = resolve(moduleDir, "../../../..");
64
+ if (
65
+ import.meta.url.endsWith("/src/sandbox/codemode-client.ts") &&
66
+ existsSync(join(root, "packages/ogtool/src/cli.ts"))
67
+ ) {
68
+ return await buildManagedCodemodeClient(root);
69
+ }
70
+ const candidates = [
71
+ join(moduleDir, "assets/codemode-client.json"),
72
+ join(moduleDir, "../assets/codemode-client.json"),
73
+ join(moduleDir, "../../dist/assets/codemode-client.json"),
74
+ ];
75
+ for (const path of candidates) {
76
+ if (!existsSync(path)) continue;
77
+ const value = JSON.parse(readFileSync(path, "utf8")) as ManagedCodemodeClient;
78
+ if (
79
+ !value ||
80
+ typeof value !== "object" ||
81
+ value.version !== 1 ||
82
+ Object.keys(value).sort().join(",") !== "files,version" ||
83
+ typeof value.files?.ogtool !== "string" ||
84
+ typeof value.files?.["client.mjs"] !== "string" ||
85
+ value.files?.["package.json"] !== '{"type":"commonjs"}\n' ||
86
+ Object.keys(value.files).sort().join(",") !== "client.mjs,ogtool,package.json"
87
+ ) {
88
+ throw new Error(
89
+ "Invalid deployment-owned managed Codemode client asset; rebuild the worker release",
90
+ );
91
+ }
92
+ return value;
93
+ }
94
+ // Published runtimes must carry their own exact bytes.
95
+ throw new Error(
96
+ "Managed Codemode client asset missing; rebuild and deploy the matching worker/runtime package (do not install latest or replace the sandbox)",
97
+ );
98
+ })());
99
+ }
100
+
101
+ function quote(value: string): string {
102
+ return `'${value.replace(/'/g, "'\\''")}'`;
103
+ }
104
+
105
+ /** Immutable per-release paths permit old/new workers to share a warm box. */
106
+ export async function installManagedCodemodeClient(
107
+ session: SandboxSessionLike,
108
+ client: ManagedCodemodeClient,
109
+ run: (cmd: string) => Promise<unknown>,
110
+ runAs?: string,
111
+ workspaceRoot = "/workspace",
112
+ ): Promise<void> {
113
+ const directory = managedCodemodeClientDirectory(client, workspaceRoot);
114
+ const digest = managedCodemodeClientDigest(client);
115
+ const verify = `
116
+ const fs = require('node:fs'), crypto = require('node:crypto');
117
+ const root = ${JSON.stringify(directory)};
118
+ if (!fs.lstatSync(root).isDirectory()) process.exit(1);
119
+ for (const name of ['ogtool', 'client.mjs', 'package.json']) {
120
+ if (!fs.lstatSync(root + '/' + name).isFile()) process.exit(1);
121
+ }
122
+ fs.accessSync(root + '/ogtool', fs.constants.X_OK);
123
+ const files = {ogtool: fs.readFileSync(root + '/ogtool', 'utf8'), 'client.mjs': fs.readFileSync(root + '/client.mjs', 'utf8'), 'package.json': fs.readFileSync(root + '/package.json', 'utf8')};
124
+ if (crypto.createHash('sha256').update(JSON.stringify({version: 1, files})).digest('hex') !== ${JSON.stringify(digest)}) process.exit(1);
125
+ `;
126
+ if (sandboxCommandExitCode(await run(`node -e ${quote(verify)} 2>/dev/null`)) === 0) return;
127
+ const editor = session.createEditor?.(runAs);
128
+ if (!editor)
129
+ throw new Error(
130
+ "Managed Codemode client delivery requires sandbox file ingress; update the sandbox provider adapter",
131
+ );
132
+ const stage = `${posix.dirname(directory)}/stage-${randomUUID()}.json`;
133
+ let installationFailure: { error: unknown } | undefined;
134
+ try {
135
+ await editor.createFile({
136
+ type: "create_file",
137
+ path: stage,
138
+ diff: `+${JSON.stringify(client)}`,
139
+ });
140
+ const install = `
141
+ const fs = require('node:fs'), crypto = require('node:crypto');
142
+ const raw = fs.readFileSync(${JSON.stringify(stage)}, 'utf8').trim();
143
+ if (crypto.createHash('sha256').update(raw).digest('hex') !== ${JSON.stringify(digest)}) throw Error('Codemode client transfer integrity failure');
144
+ const data = JSON.parse(raw), root = ${JSON.stringify(directory)};
145
+ fs.mkdirSync(root, {recursive: true});
146
+ if (!fs.lstatSync(root).isDirectory()) throw Error('Codemode client directory must not be a symlink');
147
+ for (const name of ['package.json', 'ogtool', 'client.mjs']) {
148
+ const tmp = root + '/' + name + '.' + crypto.randomUUID();
149
+ fs.writeFileSync(tmp, data.files[name], {mode: 0o755, flag: 'wx'});
150
+ fs.renameSync(tmp, root + '/' + name);
151
+ }
152
+ `;
153
+ const result = await run(`node -e ${quote(install)} && node -e ${quote(verify)}`);
154
+ if (sandboxCommandExitCode(result) !== 0) {
155
+ throw new Error(
156
+ "Managed Codemode client delivery failed; verify Node and sandbox file ingress, then retry with the same worker release",
157
+ );
158
+ }
159
+ } catch (error) {
160
+ installationFailure = { error };
161
+ }
162
+ let cleanupFailure: Error | undefined;
163
+ try {
164
+ await editor.deleteFile({ type: "delete_file", path: stage });
165
+ } catch (editorFailure) {
166
+ // A provider can support ingress but lose its delete reply. Remove only
167
+ // this exact attempt's staging file, through the same command fence.
168
+ try {
169
+ const cleanup = await run(
170
+ `node -e ${quote(`require('node:fs').rmSync(${JSON.stringify(stage)}, {force: true})`)}`,
171
+ );
172
+ if (sandboxCommandExitCode(cleanup) !== 0) {
173
+ throw new Error("Staging file removal failed", { cause: editorFailure });
174
+ }
175
+ } catch (commandFailure) {
176
+ cleanupFailure = new AggregateError(
177
+ [editorFailure, commandFailure],
178
+ "Both cleanup paths failed",
179
+ { cause: commandFailure },
180
+ );
181
+ }
182
+ }
183
+ if (cleanupFailure) {
184
+ throw new AggregateError(
185
+ installationFailure ? [installationFailure.error, cleanupFailure] : [cleanupFailure],
186
+ "Managed Codemode client staging cleanup failed; restore sandbox file ingress/command execution before retrying",
187
+ { cause: installationFailure?.error ?? cleanupFailure },
188
+ );
189
+ }
190
+ if (installationFailure) throw installationFailure.error;
191
+ }
192
+
193
+ export function managedCodemodeClientEnvironment(directory: string): string[] {
194
+ return [
195
+ // A routing proxy can switch to a Connected Machine during the attempt.
196
+ // Its native client is projected by the connection-bound exec transport.
197
+ 'if [ -z "${OPENGENI_CODEMODE_NATIVE_CLIENT:-}" ]; then',
198
+ ` export PATH=${quote(directory)}:"$PATH"`,
199
+ ` export OPENGENI_CODEMODE_CLIENT_MODULE=${quote(`${directory}/client.mjs`)}`,
200
+ "else",
201
+ " unset OPENGENI_CODEMODE_CLIENT_MODULE",
202
+ "fi",
203
+ ];
204
+ }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { posix as posixPath } from "node:path";
3
+ import { managedCodemodeClientEnvironment } from "./codemode-client";
3
4
 
4
5
  import type {
5
6
  ExecCommandArgs,
@@ -86,10 +87,12 @@ export function withCodemodeTokenEnvironment(
86
87
  cmd: string,
87
88
  tokenFile: string,
88
89
  codemodeUrl?: string,
90
+ clientDirectory?: string,
89
91
  ): string {
90
92
  return [
91
93
  `export OPENGENI_CODEMODE_TOKEN_FILE=${shellCodemodePath(tokenFile)}`,
92
94
  ...(codemodeUrl ? [`export OPENGENI_CODEMODE_URL=${shellQuote(codemodeUrl)}`] : []),
95
+ ...(clientDirectory ? managedCodemodeClientEnvironment(clientDirectory) : []),
93
96
  cmd,
94
97
  ].join("\n");
95
98
  }
@@ -99,6 +102,7 @@ export function withCodemodeTokenSession<T extends object>(
99
102
  session: T,
100
103
  tokenFile: string,
101
104
  codemodeUrl?: string,
105
+ clientDirectory?: string,
102
106
  ): T {
103
107
  return new Proxy(session, {
104
108
  get(target, property, receiver) {
@@ -110,7 +114,7 @@ export function withCodemodeTokenSession<T extends object>(
110
114
  return async (args: ExecCommandArgs) =>
111
115
  await command.call(target, {
112
116
  ...args,
113
- cmd: withCodemodeTokenEnvironment(args.cmd, tokenFile, codemodeUrl),
117
+ cmd: withCodemodeTokenEnvironment(args.cmd, tokenFile, codemodeUrl, clientDirectory),
114
118
  });
115
119
  }
116
120
  const value = Reflect.get(target, property, receiver) as unknown;
@@ -124,12 +128,13 @@ export function withCodemodeTokenClient(
124
128
  client: SandboxClient,
125
129
  tokenFile: string,
126
130
  codemodeUrl?: string,
131
+ clientDirectory?: string,
127
132
  ): SandboxClient {
128
133
  const decorated = new WeakMap<object, SandboxSessionLike>();
129
134
  const wrap = <T extends SandboxSessionLike>(session: T): T => {
130
135
  const existing = decorated.get(session);
131
136
  if (existing) return existing as T;
132
- const wrapped = withCodemodeTokenSession(session, tokenFile, codemodeUrl);
137
+ const wrapped = withCodemodeTokenSession(session, tokenFile, codemodeUrl, clientDirectory);
133
138
  decorated.set(session, wrapped);
134
139
  return wrapped;
135
140
  };
@@ -86,6 +86,12 @@ import type { ExposedPortEndpoint } from "./stream-port";
86
86
  // a single agent-loop-free entrypoint. They physically live in @opengeni/config
87
87
  // (moving them into runtime would create a config→runtime cycle — ledger CR8).
88
88
  export { collectSandboxEnvironment, parseExposedPorts } from "@opengeni/config";
89
+ export {
90
+ SandboxMaterializationVerificationError,
91
+ materializationVerificationDiagnostic,
92
+ type MaterializationFailureReason,
93
+ type MaterializationVerificationDiagnostic,
94
+ } from "./materialization-verification-error";
89
95
  export { sendCommandInput, type CommandInputSession } from "./command-input";
90
96
  export { OpStreamExecClient, type OpStreamOutputFrame } from "./selfhosted/op-stream";
91
97
 
@@ -593,6 +599,7 @@ export {
593
599
  type RoutingSandboxFirstOperationPhaseObservation,
594
600
  type RoutingSandboxOperationObservation,
595
601
  type RoutingSandboxOperationObserver,
602
+ type RoutingSandboxCaptureWaitObserver,
596
603
  type RoutingSandboxPhaseOutcome,
597
604
  type RoutingSandboxWaitObservation,
598
605
  type RoutingRetainedProcess,