impel-cli 0.18.15-beta.9 → 0.18.16
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 +6 -0
- package/package.json +1 -1
- package/src/agents.js +173 -8
- package/src/apps.js +5 -3
- package/src/windowsApps.js +3 -2
package/README.md
CHANGED
|
@@ -98,6 +98,12 @@ Names include the tenant, such as `Impel Claude (Acme)` and
|
|
|
98
98
|
`Impel ChatGPT (Acme)`. The selected CLI tenant does not affect which tenant an
|
|
99
99
|
app opens. Tenant variants can remain installed side by side.
|
|
100
100
|
|
|
101
|
+
Impel-managed apps do not run the vendors' in-app auto-updaters. The generated
|
|
102
|
+
Claude policy disables its updater, and the ChatGPT launcher disables Sparkle
|
|
103
|
+
before starting the pinned app. Run `impel update` to move managed apps to the
|
|
104
|
+
next exact vendor builds reviewed and shipped with the CLI. This does not
|
|
105
|
+
change the update settings of native personal Claude or ChatGPT installations.
|
|
106
|
+
|
|
101
107
|
Linux does not have managed desktop apps. Use the isolated CLI commands there.
|
|
102
108
|
|
|
103
109
|
## Experimental managed Cursor
|
package/package.json
CHANGED
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(),
|
package/src/apps.js
CHANGED
|
@@ -98,9 +98,11 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
98
98
|
windows: Object.freeze({
|
|
99
99
|
storeProductId: "9PLM9XGG6VKS",
|
|
100
100
|
packageName: "OpenAI.Codex",
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
// The Microsoft Store manifest identifies this exact reviewed build.
|
|
102
|
+
// Keep one Windows package version so the manifest contract and local
|
|
103
|
+
// AppX validation cannot silently drift apart again.
|
|
104
|
+
packageVersion: "26.727.6591.0",
|
|
105
|
+
codexVersion: "0.146.0-alpha.9.2",
|
|
104
106
|
publisherId: "2p2nqsd0c76g0",
|
|
105
107
|
executable: "app\\ChatGPT.exe",
|
|
106
108
|
updateManifestUrl: "https://persistent.oaistatic.com/codex-app-prod/windows-store-update.json",
|
package/src/windowsApps.js
CHANGED
|
@@ -411,8 +411,9 @@ export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies =
|
|
|
411
411
|
return { binary: pinnedBefore, action: "existing", result: null };
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
// `impel update`
|
|
415
|
-
// be installed, not sent to
|
|
414
|
+
// `impel update` converges an older official Store package to the current
|
|
415
|
+
// reviewed pin. A missing Store package must be installed, not sent to
|
|
416
|
+
// `winget upgrade` (which returns 0x8A150014).
|
|
416
417
|
const shouldUpdate = Boolean(update && before);
|
|
417
418
|
// The msstore source rejects its AppX package version as a winget --version
|
|
418
419
|
// selector. Install the current Store package, then fail closed below unless
|