impel-cli 0.18.15-beta.8 → 0.18.15
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 +0 -49
- package/package.json +1 -2
- package/src/agents.js +173 -8
- package/src/apps.js +43 -211
- package/src/commands/apps.js +8 -43
- package/src/commands/converge.js +3 -18
- package/src/commands/experimental.js +2 -8
- package/src/commands/launch.js +2 -11
- package/src/commands/status.js +2 -3
- package/src/commands/update.js +11 -23
- package/src/macSetup.js +38 -20
- package/src/provisioning.js +4 -10
- package/src/skills.js +8 -24
- package/src/updates.js +10 -57
- package/src/windowsApps.js +17 -61
- package/src/windowsSetup.js +4 -7
- package/docs/experimental-managed-cursor.md +0 -205
- package/src/commands/cursorExperimental.js +0 -192
- package/src/cursorLocal.js +0 -1549
- package/src/vendorCliBinaries.js +0 -121
- package/src/vendorCliVersions.js +0 -7
package/README.md
CHANGED
|
@@ -100,50 +100,6 @@ app opens. Tenant variants can remain installed side by side.
|
|
|
100
100
|
|
|
101
101
|
Linux does not have managed desktop apps. Use the isolated CLI commands there.
|
|
102
102
|
|
|
103
|
-
## Experimental managed Cursor
|
|
104
|
-
|
|
105
|
-
On macOS, a hidden experiment can prepare an isolated Cursor runtime for the
|
|
106
|
-
selected Impel tenant:
|
|
107
|
-
|
|
108
|
-
```sh
|
|
109
|
-
impel experimental cursor prepare
|
|
110
|
-
impel experimental cursor open [workspace]
|
|
111
|
-
impel experimental cursor status
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
The experiment never modifies `/Applications/Cursor.app` or the native Cursor
|
|
115
|
-
profile. It verifies the installed stable app's Developer ID signature, mirrors
|
|
116
|
-
it into the selected tenant's Impel-managed directory, enables Cursor's bundled
|
|
117
|
-
local-agent runtime with fail-closed fixed-width patches, verifies extension
|
|
118
|
-
integrity and signing, and prepares a tenant-only home, user-data directory,
|
|
119
|
-
and extension directory. Cursor's local-mode profile-import service is disabled
|
|
120
|
-
so it cannot inspect or copy the native Cursor profile. The managed bundle
|
|
121
|
-
recomputes Cursor's signed-source checksum table after its reviewed patches, and
|
|
122
|
-
the launcher uses Cursor's process-lifetime secret store because no persistent
|
|
123
|
-
Cursor account or provider secret belongs in this tenant runtime.
|
|
124
|
-
|
|
125
|
-
The native Cursor installation continues to update through Cursor. Every
|
|
126
|
-
`prepare` inspects it again and atomically rebuilds the managed copy
|
|
127
|
-
when a compatible version or commit changes. If Cursor changes any reviewed
|
|
128
|
-
local-agent, provider-priority, or model-transport source contract, Impel keeps
|
|
129
|
-
the previous managed generation intact and refuses the update until the patch is
|
|
130
|
-
reviewed. `experimental cursor open` performs this prepare step before every
|
|
131
|
-
launch. A real packaged GUI battle test proves that Cursor 3.12.17 can run
|
|
132
|
-
Agent through the selected tenant's gateway with Cursor's own non-secret smoke
|
|
133
|
-
identity. The logical `default` model is resolved by the gateway to its
|
|
134
|
-
registered Cursor default; explicit gateway models continue to use Responses.
|
|
135
|
-
|
|
136
|
-
This is not complete hosted-Cursor parity or an offline Cursor.
|
|
137
|
-
The managed local transport supports Agent chat, edits, terminal tools, MCP,
|
|
138
|
-
vision, reasoning, and gateway model discovery, and projects the live tenant
|
|
139
|
-
catalog into Cursor's model picker, but the current vendor build explicitly
|
|
140
|
-
disables hosted-only features such as Tab completion, cloud Background Agents,
|
|
141
|
-
shared chats/canvases, Bugbot, and server-side repository indexing in local
|
|
142
|
-
mode. Enabling those paths would either send work to Cursor-hosted models or
|
|
143
|
-
require additional unpublished Cursor protocols, so the experiment does not
|
|
144
|
-
claim or silently emulate them. See
|
|
145
|
-
[`docs/experimental-managed-cursor.md`](docs/experimental-managed-cursor.md).
|
|
146
|
-
|
|
147
103
|
## Use the CLI
|
|
148
104
|
|
|
149
105
|
Select the tenant used by CLI launches:
|
|
@@ -223,11 +179,6 @@ impel update --no-recovery # disable recovery for this run
|
|
|
223
179
|
schema verified by that CLI. It does not mean cloning an untested moving vendor
|
|
224
180
|
release.
|
|
225
181
|
|
|
226
|
-
Stable installations follow npm's `latest` tag. Prerelease installations made
|
|
227
|
-
from `impel-cli@next` remain on the `next` channel during `impel update`. If npm
|
|
228
|
-
metadata is temporarily unavailable, update preserves the installed CLI build
|
|
229
|
-
and continues tenant reconciliation without attempting a downgrade.
|
|
230
|
-
|
|
231
182
|
## Status and diagnosis
|
|
232
183
|
|
|
233
184
|
```sh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "impel-cli",
|
|
3
|
-
"version": "0.18.15
|
|
3
|
+
"version": "0.18.15",
|
|
4
4
|
"description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
15
|
-
"docs/experimental-managed-cursor.md",
|
|
16
15
|
"src",
|
|
17
16
|
"README.md"
|
|
18
17
|
],
|
package/src/agents.js
CHANGED
|
@@ -21,6 +21,7 @@ export const NATIVE_AGENT_LIST_TOOL = "impel_specialists-list_native_agents";
|
|
|
21
21
|
export const NATIVE_AGENT_START_TOOL = "impel_specialists-start_native_agent_run";
|
|
22
22
|
export const NATIVE_AGENT_READ_TOOL = "impel_specialists-read_native_agent_run";
|
|
23
23
|
export const MANAGED_AGENT_MCP_SERVER = "impel_agent";
|
|
24
|
+
export const MANAGED_AGENT_MANIFEST_VERSION = 4;
|
|
24
25
|
|
|
25
26
|
const NATIVE_AGENT_TOOL_NAMES = [
|
|
26
27
|
NATIVE_AGENT_LIST_TOOL,
|
|
@@ -320,7 +321,7 @@ function generatedClientAgentNames(client, agents) {
|
|
|
320
321
|
});
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
function
|
|
324
|
+
function claudeAdapterInstructions(tenantId, agent) {
|
|
324
325
|
const toolName = nativeToolName;
|
|
325
326
|
const contextRequirement = agent.requiredContext.length
|
|
326
327
|
? ` Required context keys are ${JSON.stringify(agent.requiredContext)}; if any are absent, ask for them before starting the run.`
|
|
@@ -333,7 +334,7 @@ function adapterInstructions(tenantId, agent) {
|
|
|
333
334
|
`Do not perform the assigned task yourself and do not delegate to any other agent.`,
|
|
334
335
|
`First call ${toolName(NATIVE_AGENT_LIST_TOOL)} and verify that the exact agentId is still available with sideEffects ${JSON.stringify(agent.sideEffects)}. If it is unavailable or its policy excludes the request, stop with that explicit error.`,
|
|
335
336
|
sideEffectInstruction,
|
|
336
|
-
`Call ${toolName(NATIVE_AGENT_START_TOOL)} exactly once with agentId ${JSON.stringify(agent.agentId)}, scopeParam ${JSON.stringify(agent.scopeParam)}, task set to the complete assigned task, context
|
|
337
|
+
`Call ${toolName(NATIVE_AGENT_START_TOOL)} exactly once with agentId ${JSON.stringify(agent.agentId)}, scopeParam ${JSON.stringify(agent.scopeParam)}, task set to the complete assigned task, optional context set to one string containing all supplied context (omit it when no context was supplied), contextKeys naming the context fields present in that string, confirmedSideEffects as directed above, and one stable idempotencyKey that you reuse for this logical task.${contextRequirement}`,
|
|
337
338
|
`Then call ${toolName(NATIVE_AGENT_READ_TOOL)} with the returned runId and waitSeconds 20 until the run reaches a terminal state.`,
|
|
338
339
|
`When it succeeds, return result.finalText faithfully as the answer. When it fails, return the durable runId, preserved output, and error. Never invent or independently synthesize a replacement result.`,
|
|
339
340
|
].join(" ");
|
|
@@ -343,6 +344,170 @@ function nativeToolName(toolName) {
|
|
|
343
344
|
return `mcp__${MANAGED_AGENT_MCP_SERVER}__${toolName}`;
|
|
344
345
|
}
|
|
345
346
|
|
|
347
|
+
const CODEX_TASK_PLACEHOLDER = "__IMPEL_COMPLETE_ASSIGNED_TASK_JSON__";
|
|
348
|
+
const CODEX_CONTEXT_PLACEHOLDER = "__IMPEL_OPTIONAL_CONTEXT_STRING_OR_NULL_JSON__";
|
|
349
|
+
const CODEX_CONTEXT_KEYS_PLACEHOLDER = "__IMPEL_SUPPLIED_CONTEXT_KEYS_JSON__";
|
|
350
|
+
const CODEX_IDEMPOTENCY_KEY_PLACEHOLDER = "__IMPEL_LOGICAL_INVOCATION_IDEMPOTENCY_KEY_JSON__";
|
|
351
|
+
|
|
352
|
+
export function renderCodexAdapterOrchestration(tenantId, agent) {
|
|
353
|
+
const expectedTenantId = normalizeTenantId(tenantId);
|
|
354
|
+
const nestedToolName = (toolName) => nativeToolName(toolName).replaceAll("-", "_");
|
|
355
|
+
const listTool = nestedToolName(NATIVE_AGENT_LIST_TOOL);
|
|
356
|
+
const startTool = nestedToolName(NATIVE_AGENT_START_TOOL);
|
|
357
|
+
const readTool = nestedToolName(NATIVE_AGENT_READ_TOOL);
|
|
358
|
+
return [
|
|
359
|
+
'// @exec: {"yield_time_ms": 30000, "max_output_tokens": 30000}',
|
|
360
|
+
`const assignedTask = ${CODEX_TASK_PLACEHOLDER};`,
|
|
361
|
+
`const suppliedContext = ${CODEX_CONTEXT_PLACEHOLDER};`,
|
|
362
|
+
`const suppliedContextKeys = ${CODEX_CONTEXT_KEYS_PLACEHOLDER};`,
|
|
363
|
+
`const idempotencyKey = ${CODEX_IDEMPOTENCY_KEY_PLACEHOLDER};`,
|
|
364
|
+
`const expectedTenantId = ${JSON.stringify(expectedTenantId)};`,
|
|
365
|
+
`const expectedAgent = ${JSON.stringify(agent)};`,
|
|
366
|
+
"const waitSeconds = 20; // Compatible fallback until the bound server advertises a larger ceiling.",
|
|
367
|
+
`const listToolName = ${JSON.stringify(listTool)};`,
|
|
368
|
+
`const startToolName = ${JSON.stringify(startTool)};`,
|
|
369
|
+
`const readToolName = ${JSON.stringify(readTool)};`,
|
|
370
|
+
"",
|
|
371
|
+
"function stableValue(value) {",
|
|
372
|
+
" if (Array.isArray(value)) return value.map(stableValue);",
|
|
373
|
+
' if (!value || typeof value !== "object") return value;',
|
|
374
|
+
" const result = {};",
|
|
375
|
+
" for (const key of Object.keys(value).sort()) result[key] = stableValue(value[key]);",
|
|
376
|
+
" return result;",
|
|
377
|
+
"}",
|
|
378
|
+
"",
|
|
379
|
+
"function contentText(response) {",
|
|
380
|
+
' return response?.content?.find((item) => item?.type === "text" && typeof item.text === "string")?.text;',
|
|
381
|
+
"}",
|
|
382
|
+
"",
|
|
383
|
+
"function toolPayload(response, label) {",
|
|
384
|
+
' if (!response || typeof response !== "object") throw new Error(label + " returned no result");',
|
|
385
|
+
' if (response.isError) throw new Error(contentText(response) || label + " failed");',
|
|
386
|
+
' if (response.structuredContent && typeof response.structuredContent === "object") return response.structuredContent;',
|
|
387
|
+
" const serialized = contentText(response);",
|
|
388
|
+
" if (serialized !== undefined) {",
|
|
389
|
+
" try {",
|
|
390
|
+
" return JSON.parse(serialized);",
|
|
391
|
+
" } catch {",
|
|
392
|
+
' throw new Error(label + " returned invalid JSON");',
|
|
393
|
+
" }",
|
|
394
|
+
" }",
|
|
395
|
+
" return response;",
|
|
396
|
+
"}",
|
|
397
|
+
"",
|
|
398
|
+
"function preservedOutput(run) {",
|
|
399
|
+
' if (run && Object.prototype.hasOwnProperty.call(run, "output")) return run.output;',
|
|
400
|
+
' if (run && Object.prototype.hasOwnProperty.call(run, "result")) return run.result;',
|
|
401
|
+
" return null;",
|
|
402
|
+
"}",
|
|
403
|
+
"",
|
|
404
|
+
"function errorValue(error, fallback) {",
|
|
405
|
+
" if (error === undefined || error === null) return fallback;",
|
|
406
|
+
' if (error instanceof Error) return error.message;',
|
|
407
|
+
" return error;",
|
|
408
|
+
"}",
|
|
409
|
+
"",
|
|
410
|
+
"async function orchestrate() {",
|
|
411
|
+
" let runId = null;",
|
|
412
|
+
" let latestRun = null;",
|
|
413
|
+
" try {",
|
|
414
|
+
' if (typeof assignedTask !== "string" || !assignedTask.trim()) throw new Error("the complete assigned task is required");',
|
|
415
|
+
' if (suppliedContext !== null && typeof suppliedContext !== "string") throw new Error("supplied context must be a string or null");',
|
|
416
|
+
' if (expectedAgent.requiredContext.length && (typeof suppliedContext !== "string" || !suppliedContext.trim())) {',
|
|
417
|
+
' throw new Error("nonblank supplied context is required for required context keys");',
|
|
418
|
+
" }",
|
|
419
|
+
' if (!Array.isArray(suppliedContextKeys)) throw new Error("supplied context keys must be an array");',
|
|
420
|
+
' if (typeof idempotencyKey !== "string" || !/^[A-Za-z0-9._:-]{16,160}$/u.test(idempotencyKey)) {',
|
|
421
|
+
' throw new Error("a unique stable logical-invocation idempotencyKey is required");',
|
|
422
|
+
" }",
|
|
423
|
+
" const missingContext = expectedAgent.requiredContext.filter((key) =>",
|
|
424
|
+
" !suppliedContextKeys.includes(key)",
|
|
425
|
+
" );",
|
|
426
|
+
' if (missingContext.length) throw new Error("missing required context: " + missingContext.join(", "));',
|
|
427
|
+
"",
|
|
428
|
+
" const catalog = toolPayload(await tools[listToolName]({}), \"native-agent catalog\");",
|
|
429
|
+
' if (catalog.orgId !== expectedTenantId) throw new Error("native-agent catalog tenant mismatch");',
|
|
430
|
+
' if (!Array.isArray(catalog.agents)) throw new Error("native-agent catalog returned no agents");',
|
|
431
|
+
" const matches = catalog.agents.filter((candidate) =>",
|
|
432
|
+
" candidate?.agentId === expectedAgent.agentId && candidate?.scopeParam === expectedAgent.scopeParam",
|
|
433
|
+
" );",
|
|
434
|
+
' if (matches.length !== 1) throw new Error("exact native-agent binding is unavailable");',
|
|
435
|
+
" const actualAgent = {};",
|
|
436
|
+
" for (const key of Object.keys(expectedAgent)) actualAgent[key] = matches[0][key];",
|
|
437
|
+
" if (JSON.stringify(stableValue(actualAgent)) !== JSON.stringify(stableValue(expectedAgent))) {",
|
|
438
|
+
' throw new Error("native-agent catalog policy mismatch");',
|
|
439
|
+
" }",
|
|
440
|
+
"",
|
|
441
|
+
" const startArguments = {",
|
|
442
|
+
" agentId: expectedAgent.agentId,",
|
|
443
|
+
" scopeParam: expectedAgent.scopeParam,",
|
|
444
|
+
" task: assignedTask,",
|
|
445
|
+
" contextKeys: suppliedContextKeys,",
|
|
446
|
+
" idempotencyKey,",
|
|
447
|
+
" };",
|
|
448
|
+
" if (suppliedContext !== null) startArguments.context = suppliedContext;",
|
|
449
|
+
' if (expectedAgent.sideEffects === "writes") startArguments.confirmedSideEffects = true;',
|
|
450
|
+
" latestRun = toolPayload(await tools[startToolName](startArguments), \"native-agent start\");",
|
|
451
|
+
' if (typeof latestRun.runId !== "string" || !latestRun.runId) throw new Error("native-agent start returned no runId");',
|
|
452
|
+
" runId = latestRun.runId;",
|
|
453
|
+
"",
|
|
454
|
+
" for (;;) {",
|
|
455
|
+
" const observed = toolPayload(",
|
|
456
|
+
" await tools[readToolName]({ runId, waitSeconds }),",
|
|
457
|
+
' "native-agent read",',
|
|
458
|
+
" );",
|
|
459
|
+
' if (observed.runId !== undefined && observed.runId !== runId) throw new Error("native-agent read returned a different runId");',
|
|
460
|
+
" latestRun = observed;",
|
|
461
|
+
' if (observed.status === "succeeded") {',
|
|
462
|
+
' const finalText = observed.result?.finalText;',
|
|
463
|
+
' if (typeof finalText !== "string") throw new Error("succeeded native-agent run returned no finalText");',
|
|
464
|
+
" text(finalText);",
|
|
465
|
+
" return;",
|
|
466
|
+
" }",
|
|
467
|
+
' if (observed.status === "failed") {',
|
|
468
|
+
" text(JSON.stringify({",
|
|
469
|
+
" runId,",
|
|
470
|
+
" output: preservedOutput(observed),",
|
|
471
|
+
' error: errorValue(observed.error, "native-agent run failed"),',
|
|
472
|
+
" }));",
|
|
473
|
+
" return;",
|
|
474
|
+
" }",
|
|
475
|
+
" }",
|
|
476
|
+
" } catch (error) {",
|
|
477
|
+
" text(JSON.stringify({",
|
|
478
|
+
" runId,",
|
|
479
|
+
" output: preservedOutput(latestRun),",
|
|
480
|
+
' error: errorValue(error, "native-agent adapter failed"),',
|
|
481
|
+
" }));",
|
|
482
|
+
" }",
|
|
483
|
+
"}",
|
|
484
|
+
"",
|
|
485
|
+
"await orchestrate();",
|
|
486
|
+
].join("\n");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function codexAdapterInstructions(tenantId, agent) {
|
|
490
|
+
const contextRequirement = agent.requiredContext.length
|
|
491
|
+
? ` Required context keys are ${JSON.stringify(agent.requiredContext)}; if any are absent, ask for them before starting the run.`
|
|
492
|
+
: "";
|
|
493
|
+
const sideEffectInstruction = agent.sideEffects === "writes"
|
|
494
|
+
? `The catalog declares that this agent writes user or workspace data. Use this adapter only when the user explicitly selected this exact agent for the current request; that explicit selection is the required side-effect confirmation. If the agent was chosen automatically or the selection is ambiguous, do not run the orchestration and ask the user to select this exact agent explicitly.`
|
|
495
|
+
: `The catalog declares that this agent is read-only; the orchestration omits confirmedSideEffects.`;
|
|
496
|
+
const source = renderCodexAdapterOrchestration(tenantId, agent);
|
|
497
|
+
return [
|
|
498
|
+
`You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
|
|
499
|
+
`Callers must spawn this explicit custom Codex agent with fork_turns="none" and must relay your result verbatim; this is caller guidance and cannot enforce host spawn behavior.`,
|
|
500
|
+
`Do not perform the assigned task yourself, do not delegate to any other agent, and do not independently synthesize or rewrite the result.`,
|
|
501
|
+
`Confirm that the assigned request fits the cataloged capabilities ${JSON.stringify(agent.capabilities)} and none of the exclusions ${JSON.stringify(agent.exclusions)} before starting.${contextRequirement}`,
|
|
502
|
+
sideEffectInstruction,
|
|
503
|
+
`Invoke functions.exec exactly once for the orchestration below. Do not call the MCP tools directly or select a separate MCP call for any poll. Replace ${CODEX_TASK_PLACEHOLDER} with a JSON string literal for the complete assigned task. Replace ${CODEX_CONTEXT_PLACEHOLDER} with one JSON string literal containing all supplied caller context, or with null when no context was supplied; never use an object or array. Replace ${CODEX_CONTEXT_KEYS_PLACEHOLDER} with a JSON array naming the context fields present in that string, or [] when context is absent. Replace ${CODEX_IDEMPOTENCY_KEY_PLACEHOLDER} with one new opaque idempotency key for this logical invocation: choose it exactly once, reuse it unchanged for any retry of this invocation, and never reuse it for a separate request even when task and context are identical. Then pass the raw JavaScript without Markdown fences.`,
|
|
504
|
+
`The JavaScript validates the exact tenant, agent binding, and catalog policy; passes the one stable logical-invocation idempotencyKey; starts exactly once; and polls deterministically with the compatible 20-second server wait until status is succeeded or failed. If functions.exec yields a running cell, use functions.wait with max_tokens 30000 only to resume that same orchestration; never start another orchestration or poll the MCP tool yourself.`,
|
|
505
|
+
`After the orchestration completes, return its single text output verbatim with no preface, rewriting, Markdown changes, or independent synthesis. A successful output is result.finalText exactly. A failure output preserves the durable runId, output, and error.`,
|
|
506
|
+
"",
|
|
507
|
+
source,
|
|
508
|
+
].join("\n\n");
|
|
509
|
+
}
|
|
510
|
+
|
|
346
511
|
function renderClaudeAgent({ tenantId, agent, name, invocation }) {
|
|
347
512
|
const description = `Explicitly runs ${agent.title} for Impel tenant ${tenantId}${agent.sideEffects === "writes" ? " (may write user or workspace data)" : " (read-only)"}: ${agent.description}`.slice(0, 900);
|
|
348
513
|
const lines = [
|
|
@@ -362,14 +527,14 @@ function renderClaudeAgent({ tenantId, agent, name, invocation }) {
|
|
|
362
527
|
...Object.entries(invocation.env || {}).map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`),
|
|
363
528
|
"---",
|
|
364
529
|
"",
|
|
365
|
-
|
|
530
|
+
claudeAdapterInstructions(tenantId, agent),
|
|
366
531
|
"",
|
|
367
532
|
];
|
|
368
533
|
return lines.join("\n");
|
|
369
534
|
}
|
|
370
535
|
|
|
371
536
|
function renderCodexAgent({ tenantId, agent, name, invocation }) {
|
|
372
|
-
const description = `
|
|
537
|
+
const description = `Explicit custom agent: callers must use fork_turns="none" and relay its result verbatim. Runs ${agent.title} for Impel tenant ${tenantId}${agent.sideEffects === "writes" ? " (may write user or workspace data)" : " (read-only)"}: ${agent.description}`.slice(0, 900);
|
|
373
538
|
const envEntries = Object.entries(invocation.env || {})
|
|
374
539
|
.map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`)
|
|
375
540
|
.join(", ");
|
|
@@ -377,7 +542,7 @@ function renderCodexAgent({ tenantId, agent, name, invocation }) {
|
|
|
377
542
|
`name = ${JSON.stringify(name)}`,
|
|
378
543
|
`description = ${JSON.stringify(description)}`,
|
|
379
544
|
'sandbox_mode = "read-only"',
|
|
380
|
-
`developer_instructions = ${JSON.stringify(
|
|
545
|
+
`developer_instructions = ${JSON.stringify(codexAdapterInstructions(tenantId, agent))}`,
|
|
381
546
|
"",
|
|
382
547
|
`[mcp_servers.${MANAGED_AGENT_MCP_SERVER}]`,
|
|
383
548
|
`command = ${JSON.stringify(invocation.command)}`,
|
|
@@ -440,7 +605,7 @@ function readManifest(manifestPath) {
|
|
|
440
605
|
|
|
441
606
|
function profileIsFresh(profile, tenantId, now, ttlMs) {
|
|
442
607
|
const manifest = readManifest(path.join(profile.root, "agents", MANAGED_AGENT_DIRECTORY, MANAGED_AGENT_MANIFEST));
|
|
443
|
-
if (!manifest || manifest.version !==
|
|
608
|
+
if (!manifest || manifest.version !== MANAGED_AGENT_MANIFEST_VERSION || manifest.tenantId !== tenantId) return false;
|
|
444
609
|
const syncedAt = Date.parse(manifest.syncedAt || "");
|
|
445
610
|
if (!Number.isFinite(syncedAt) || now - syncedAt >= ttlMs) return false;
|
|
446
611
|
return manifest.files.every((fileName) =>
|
|
@@ -463,7 +628,7 @@ export function syncAgentProfile({ client, root, label, tenantId, agents, now =
|
|
|
463
628
|
const prior = readManifest(manifestPath);
|
|
464
629
|
const rendered = renderManagedAgents(client, tenantId, agents);
|
|
465
630
|
const priorFiles = new Set(prior?.files || []);
|
|
466
|
-
const priorUsesDiscoveryRoot =
|
|
631
|
+
const priorUsesDiscoveryRoot = [2, 3, MANAGED_AGENT_MANIFEST_VERSION].includes(prior?.version);
|
|
467
632
|
|
|
468
633
|
// Native clients discover standalone definitions directly under `agents/`.
|
|
469
634
|
// Preflight every destination before writing so an unmanaged file with the
|
|
@@ -505,7 +670,7 @@ export function syncAgentProfile({ client, root, label, tenantId, agents, now =
|
|
|
505
670
|
}
|
|
506
671
|
}
|
|
507
672
|
atomicPrivateWrite(manifestPath, `${JSON.stringify({
|
|
508
|
-
version:
|
|
673
|
+
version: MANAGED_AGENT_MANIFEST_VERSION,
|
|
509
674
|
tenantId,
|
|
510
675
|
client,
|
|
511
676
|
syncedAt: new Date(now).toISOString(),
|