impel-cli 0.20.0-beta.0 → 0.20.1

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/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.0-beta.0",
3
+ "version": "0.20.1",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
- "packageManager": "pnpm@11.17.0",
7
6
  "bin": {
8
7
  "impel": "bin/impel.js"
9
8
  },
@@ -20,9 +19,6 @@
20
19
  "engines": {
21
20
  "node": ">=18.0.0"
22
21
  },
23
- "scripts": {
24
- "test": "node --test"
25
- },
26
22
  "license": "UNLICENSED",
27
23
  "publishConfig": {
28
24
  "access": "public",
@@ -31,5 +27,8 @@
31
27
  "repository": {
32
28
  "type": "git",
33
29
  "url": "git+https://github.com/UseImpel/impel-cli.git"
30
+ },
31
+ "scripts": {
32
+ "test": "node --test"
34
33
  }
35
- }
34
+ }
package/src/agents.js CHANGED
@@ -10,24 +10,8 @@ import os from "node:os";
10
10
  import path from "node:path";
11
11
 
12
12
  import { normalizeGatewayUrl, redactSecretText } from "./config.js";
13
- import {
14
- adapterAnswerHardCompletionGuidance,
15
- adapterAnswerSoftCompletionGuidance,
16
- claudeAnswerHardCompletionGuidance,
17
- claudeAnswerSoftCompletionGuidance,
18
- usesDirectAnswer,
19
- } from "./directAnswer.js";
20
13
  import { impelMcpInvocation } from "./selfInvocation.js";
21
14
  import { normalizeTenantId } from "./tenants.js";
22
- import {
23
- adapterCallerSpawnGuidance,
24
- adapterHardCompletionGuidance,
25
- adapterSoftCompletionGuidance,
26
- claudeHardCompletionGuidance,
27
- claudeSoftCompletionGuidance,
28
- customAgentVerbatimDescriptionLead,
29
- usesVerbatimRelay,
30
- } from "./verbatimRelay.js";
31
15
  import { renameWithWindowsRetry } from "./windowsFs.js";
32
16
 
33
17
  export const AGENT_SYNC_TTL_MS = 6 * 60 * 60 * 1000;
@@ -36,22 +20,14 @@ export const MANAGED_AGENT_MANIFEST = ".manifest.json";
36
20
  export const NATIVE_AGENT_LIST_TOOL = "impel_specialists-list_native_agents";
37
21
  export const NATIVE_AGENT_START_TOOL = "impel_specialists-start_native_agent_run";
38
22
  export const NATIVE_AGENT_READ_TOOL = "impel_specialists-read_native_agent_run";
39
- export const NATIVE_AGENT_ANSWER_TOOL = "impel_specialists-answer_native_agent";
40
23
  export const MANAGED_AGENT_MCP_SERVER = "impel_agent";
41
- export const MANAGED_AGENT_MANIFEST_VERSION = 6;
24
+ export const MANAGED_AGENT_MANIFEST_VERSION = 4;
42
25
 
43
- const NATIVE_AGENT_SESSION_TOOL_NAMES = [
26
+ const NATIVE_AGENT_TOOL_NAMES = [
44
27
  NATIVE_AGENT_LIST_TOOL,
45
28
  NATIVE_AGENT_START_TOOL,
46
29
  NATIVE_AGENT_READ_TOOL,
47
30
  ];
48
-
49
- function nativeAgentToolNames(agent) {
50
- return usesDirectAnswer(agent)
51
- ? [NATIVE_AGENT_ANSWER_TOOL]
52
- : NATIVE_AGENT_SESSION_TOOL_NAMES;
53
- }
54
-
55
31
  const SAFE_AGENT_ID_RE = /^[A-Za-z0-9_.:-]{1,160}$/u;
56
32
  const SAFE_SCOPE_PARAM_RE = /^[A-Za-z0-9_.:-]{1,160}$/u;
57
33
  const MAX_CATALOG_ITEMS = 500;
@@ -149,12 +125,6 @@ export function normalizeNativeAgentCatalog(payload, expectedTenantId) {
149
125
  throw new Error(`native-agent catalog returned duplicate binding ${binding}`);
150
126
  }
151
127
  seenBindings.add(binding);
152
- if (agent.verbatimRelay !== undefined && typeof agent.verbatimRelay !== "boolean") {
153
- throw new Error("native-agent catalog returned an invalid verbatimRelay");
154
- }
155
- if (agent.directAnswer !== undefined && typeof agent.directAnswer !== "boolean") {
156
- throw new Error("native-agent catalog returned an invalid directAnswer");
157
- }
158
128
  return {
159
129
  agentId,
160
130
  title: boundedString(agent.title, "title", { max: 160 }),
@@ -165,8 +135,6 @@ export function normalizeNativeAgentCatalog(payload, expectedTenantId) {
165
135
  exclusions: stringList(agent.exclusions, "exclusions"),
166
136
  requiredContext: stringList(agent.requiredContext, "requiredContext"),
167
137
  sideEffects: enumString(agent.sideEffects, "sideEffects", ["read-only", "writes"]),
168
- ...(agent.verbatimRelay === true ? { verbatimRelay: true } : {}),
169
- ...(agent.directAnswer === true ? { directAnswer: true } : {}),
170
138
  };
171
139
  });
172
140
  return { orgId: tenantId, agents };
@@ -361,35 +329,14 @@ function claudeAdapterInstructions(tenantId, agent) {
361
329
  const sideEffectInstruction = agent.sideEffects === "writes"
362
330
  ? `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, so pass confirmedSideEffects true. If the agent was chosen automatically or the selection is ambiguous, do not start it and ask the user to select it explicitly.`
363
331
  : `The catalog declares that this agent is read-only; omit confirmedSideEffects.`;
364
- const callerSpawnGuidance = usesVerbatimRelay(agent)
365
- ? adapterCallerSpawnGuidance("Claude")
366
- : null;
367
- if (usesDirectAnswer(agent)) {
368
- const completionGuidance = usesVerbatimRelay(agent)
369
- ? claudeAnswerHardCompletionGuidance()
370
- : claudeAnswerSoftCompletionGuidance();
371
- return [
372
- `You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
373
- ...(callerSpawnGuidance ? [callerSpawnGuidance] : []),
374
- `Do not perform the assigned task yourself, do not delegate to any other agent, and do not call Impel start_native_agent_run or read_native_agent_run.`,
375
- `Confirm that the assigned request fits the cataloged capabilities ${JSON.stringify(agent.capabilities)} and none of the exclusions ${JSON.stringify(agent.exclusions)} before answering.${contextRequirement}`,
376
- sideEffectInstruction,
377
- `Call ${toolName(NATIVE_AGENT_ANSWER_TOOL)} exactly once with agentId ${JSON.stringify(agent.agentId)}, scopeParam ${JSON.stringify(agent.scopeParam)}, question 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, and confirmedSideEffects as directed above.`,
378
- completionGuidance,
379
- ].join(" ");
380
- }
381
- const completionGuidance = usesVerbatimRelay(agent)
382
- ? claudeHardCompletionGuidance()
383
- : claudeSoftCompletionGuidance();
384
332
  return [
385
333
  `You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
386
- ...(callerSpawnGuidance ? [callerSpawnGuidance] : []),
387
334
  `Do not perform the assigned task yourself and do not delegate to any other agent.`,
388
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.`,
389
336
  sideEffectInstruction,
390
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}`,
391
338
  `Then call ${toolName(NATIVE_AGENT_READ_TOOL)} with the returned runId and waitSeconds 20 until the run reaches a terminal state.`,
392
- completionGuidance,
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.`,
393
340
  ].join(" ");
394
341
  }
395
342
 
@@ -402,96 +349,7 @@ const CODEX_CONTEXT_PLACEHOLDER = "__IMPEL_OPTIONAL_CONTEXT_STRING_OR_NULL_JSON_
402
349
  const CODEX_CONTEXT_KEYS_PLACEHOLDER = "__IMPEL_SUPPLIED_CONTEXT_KEYS_JSON__";
403
350
  const CODEX_IDEMPOTENCY_KEY_PLACEHOLDER = "__IMPEL_LOGICAL_INVOCATION_IDEMPOTENCY_KEY_JSON__";
404
351
 
405
- function renderCodexDirectAnswerOrchestration(tenantId, agent) {
406
- const expectedTenantId = normalizeTenantId(tenantId);
407
- const nestedToolName = (toolName) => nativeToolName(toolName).replaceAll("-", "_");
408
- const answerTool = nestedToolName(NATIVE_AGENT_ANSWER_TOOL);
409
- return [
410
- '// @exec: {"yield_time_ms": 30000, "max_output_tokens": 30000}',
411
- `const assignedTask = ${CODEX_TASK_PLACEHOLDER};`,
412
- `const suppliedContext = ${CODEX_CONTEXT_PLACEHOLDER};`,
413
- `const suppliedContextKeys = ${CODEX_CONTEXT_KEYS_PLACEHOLDER};`,
414
- `const idempotencyKey = ${CODEX_IDEMPOTENCY_KEY_PLACEHOLDER};`,
415
- `const expectedTenantId = ${JSON.stringify(expectedTenantId)};`,
416
- `const expectedAgent = ${JSON.stringify(agent)};`,
417
- `const answerToolName = ${JSON.stringify(answerTool)};`,
418
- "",
419
- "function contentText(response) {",
420
- ' return response?.content?.find((item) => item?.type === "text" && typeof item.text === "string")?.text;',
421
- "}",
422
- "",
423
- "function toolPayload(response, label) {",
424
- ' if (!response || typeof response !== "object") throw new Error(label + " returned no result");',
425
- ' if (response.isError) throw new Error(contentText(response) || label + " failed");',
426
- ' if (response.structuredContent && typeof response.structuredContent === "object") return response.structuredContent;',
427
- " const serialized = contentText(response);",
428
- " if (serialized !== undefined) {",
429
- " try {",
430
- " return JSON.parse(serialized);",
431
- " } catch {",
432
- ' throw new Error(label + " returned invalid JSON");',
433
- " }",
434
- " }",
435
- " return response;",
436
- "}",
437
- "",
438
- "function extractAnswerFinalText(payload) {",
439
- ' if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;',
440
- ' for (const key of ["forUser", "answer"]) {',
441
- " const value = payload[key];",
442
- ' if (typeof value === "string" && value.trim()) return value;',
443
- " }",
444
- " return null;",
445
- "}",
446
- "",
447
- "function errorValue(error, fallback) {",
448
- " if (error === undefined || error === null) return fallback;",
449
- ' if (error instanceof Error) return error.message;',
450
- " return error;",
451
- "}",
452
- "",
453
- "async function orchestrate() {",
454
- " try {",
455
- ' if (typeof assignedTask !== "string" || !assignedTask.trim()) throw new Error("the complete assigned task is required");',
456
- ' if (suppliedContext !== null && typeof suppliedContext !== "string") throw new Error("supplied context must be a string or null");',
457
- ' if (expectedAgent.requiredContext.length && (typeof suppliedContext !== "string" || !suppliedContext.trim())) {',
458
- ' throw new Error("nonblank supplied context is required for required context keys");',
459
- " }",
460
- ' if (!Array.isArray(suppliedContextKeys)) throw new Error("supplied context keys must be an array");',
461
- " const missingContext = expectedAgent.requiredContext.filter((key) =>",
462
- " !suppliedContextKeys.includes(key)",
463
- " );",
464
- ' if (missingContext.length) throw new Error("missing required context: " + missingContext.join(", "));',
465
- "",
466
- " const answerArguments = {",
467
- " agentId: expectedAgent.agentId,",
468
- " scopeParam: expectedAgent.scopeParam,",
469
- " question: assignedTask,",
470
- " contextKeys: suppliedContextKeys,",
471
- " };",
472
- " if (suppliedContext !== null) answerArguments.context = suppliedContext;",
473
- ' if (expectedAgent.sideEffects === "writes") answerArguments.confirmedSideEffects = true;',
474
- " const answered = toolPayload(await tools[answerToolName](answerArguments), \"native-agent answer\");",
475
- " const finalText = extractAnswerFinalText(answered);",
476
- ' if (typeof finalText !== "string") throw new Error("native-agent answer returned no forUser/answer");',
477
- " text(finalText);",
478
- " } catch (error) {",
479
- " text(JSON.stringify({",
480
- ' error: errorValue(error, "native-agent answer adapter failed"),',
481
- " tenantId: expectedTenantId,",
482
- " agentId: expectedAgent.agentId,",
483
- " }));",
484
- " }",
485
- "}",
486
- "",
487
- "await orchestrate();",
488
- ].join("\n");
489
- }
490
-
491
352
  export function renderCodexAdapterOrchestration(tenantId, agent) {
492
- if (usesDirectAnswer(agent)) {
493
- return renderCodexDirectAnswerOrchestration(tenantId, agent);
494
- }
495
353
  const expectedTenantId = normalizeTenantId(tenantId);
496
354
  const nestedToolName = (toolName) => nativeToolName(toolName).replaceAll("-", "_");
497
355
  const listTool = nestedToolName(NATIVE_AGENT_LIST_TOOL);
@@ -636,61 +494,29 @@ function codexAdapterInstructions(tenantId, agent) {
636
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.`
637
495
  : `The catalog declares that this agent is read-only; the orchestration omits confirmedSideEffects.`;
638
496
  const source = renderCodexAdapterOrchestration(tenantId, agent);
639
- const callerSpawnGuidance = usesVerbatimRelay(agent)
640
- ? adapterCallerSpawnGuidance("Codex")
641
- : null;
642
- if (usesDirectAnswer(agent)) {
643
- const completionGuidance = usesVerbatimRelay(agent)
644
- ? adapterAnswerHardCompletionGuidance()
645
- : adapterAnswerSoftCompletionGuidance();
646
- return [
647
- `You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
648
- ...(callerSpawnGuidance ? [callerSpawnGuidance] : []),
649
- `Do not perform the assigned task yourself, do not delegate to any other agent, do not independently synthesize or rewrite the result, and do not call Impel start_native_agent_run or read_native_agent_run.`,
650
- `Confirm that the assigned request fits the cataloged capabilities ${JSON.stringify(agent.capabilities)} and none of the exclusions ${JSON.stringify(agent.exclusions)} before answering.${contextRequirement}`,
651
- sideEffectInstruction,
652
- `Invoke functions.exec exactly once for the orchestration below. Do not call the MCP tools directly. 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 (unused by the answer tool but kept for adapter parity): choose it exactly once. Then pass the raw JavaScript without Markdown fences.`,
653
- `The JavaScript validates required context and calls Impel answer_native_agent exactly once, returning forUser (else answer). 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 open an Impel/Eve session.`,
654
- completionGuidance,
655
- "",
656
- source,
657
- ].join("\n\n");
658
- }
659
- const completionGuidance = usesVerbatimRelay(agent)
660
- ? adapterHardCompletionGuidance()
661
- : adapterSoftCompletionGuidance();
662
497
  return [
663
498
  `You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
664
- ...(callerSpawnGuidance ? [callerSpawnGuidance] : []),
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.`,
665
500
  `Do not perform the assigned task yourself, do not delegate to any other agent, and do not independently synthesize or rewrite the result.`,
666
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}`,
667
502
  sideEffectInstruction,
668
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.`,
669
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.`,
670
- completionGuidance,
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.`,
671
506
  "",
672
507
  source,
673
508
  ].join("\n\n");
674
509
  }
675
510
 
676
- function customAgentDescription(tenantId, agent) {
677
- const sideEffectsLabel = agent.sideEffects === "writes" ? " (may write user or workspace data)" : " (read-only)";
678
- return (usesVerbatimRelay(agent)
679
- ? `${customAgentVerbatimDescriptionLead()}. Runs ${agent.title} for Impel tenant ${tenantId}${sideEffectsLabel}: ${agent.description}`
680
- : `Explicitly runs ${agent.title} for Impel tenant ${tenantId}${sideEffectsLabel}: ${agent.description}`
681
- ).slice(0, 900);
682
- }
683
-
684
511
  function renderClaudeAgent({ tenantId, agent, name, invocation }) {
685
- const description = customAgentDescription(tenantId, agent);
686
- const toolNames = nativeAgentToolNames(agent);
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);
687
513
  const lines = [
688
514
  "---",
689
515
  `name: ${JSON.stringify(name)}`,
690
516
  `description: ${JSON.stringify(description)}`,
691
517
  "model: inherit",
692
518
  "tools:",
693
- ...toolNames.map((tool) => ` - ${JSON.stringify(nativeToolName(tool))}`),
519
+ ...NATIVE_AGENT_TOOL_NAMES.map((tool) => ` - ${JSON.stringify(nativeToolName(tool))}`),
694
520
  "mcpServers:",
695
521
  ` - ${MANAGED_AGENT_MCP_SERVER}:`,
696
522
  " type: stdio",
@@ -708,8 +534,7 @@ function renderClaudeAgent({ tenantId, agent, name, invocation }) {
708
534
  }
709
535
 
710
536
  function renderCodexAgent({ tenantId, agent, name, invocation }) {
711
- const description = customAgentDescription(tenantId, agent);
712
- const toolNames = nativeAgentToolNames(agent);
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);
713
538
  const envEntries = Object.entries(invocation.env || {})
714
539
  .map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`)
715
540
  .join(", ");
@@ -722,11 +547,11 @@ function renderCodexAgent({ tenantId, agent, name, invocation }) {
722
547
  `[mcp_servers.${MANAGED_AGENT_MCP_SERVER}]`,
723
548
  `command = ${JSON.stringify(invocation.command)}`,
724
549
  `args = [${invocation.args.map((argument) => JSON.stringify(argument)).join(", ")}]`,
725
- `enabled_tools = [${toolNames.map((tool) => JSON.stringify(tool)).join(", ")}]`,
550
+ `enabled_tools = [${NATIVE_AGENT_TOOL_NAMES.map((tool) => JSON.stringify(tool)).join(", ")}]`,
726
551
  ...(envEntries ? [`env = { ${envEntries} }`] : []),
727
552
  "",
728
553
  ];
729
- for (const tool of toolNames) {
554
+ for (const tool of NATIVE_AGENT_TOOL_NAMES) {
730
555
  lines.push(`[mcp_servers.${MANAGED_AGENT_MCP_SERVER}.tools.${JSON.stringify(tool)}]`, 'approval_mode = "approve"', "");
731
556
  }
732
557
  return lines.join("\n");
@@ -803,7 +628,7 @@ export function syncAgentProfile({ client, root, label, tenantId, agents, now =
803
628
  const prior = readManifest(manifestPath);
804
629
  const rendered = renderManagedAgents(client, tenantId, agents);
805
630
  const priorFiles = new Set(prior?.files || []);
806
- const priorUsesDiscoveryRoot = [2, 3, 4, 5, MANAGED_AGENT_MANIFEST_VERSION].includes(prior?.version);
631
+ const priorUsesDiscoveryRoot = [2, 3, MANAGED_AGENT_MANIFEST_VERSION].includes(prior?.version);
807
632
 
808
633
  // Native clients discover standalone definitions directly under `agents/`.
809
634
  // Preflight every destination before writing so an unmanaged file with the
package/src/apps.js CHANGED
@@ -22,7 +22,6 @@ import { ensureClaudeSessionHooks, ensureCodexSessionHooks } from "./sessionHook
22
22
  import { ADHOC_IDENTITY, codesignIdentityArgs, desiredSigningMode, resolveSigningIdentity } from "./codesign.js";
23
23
  import { applyImpelClaudeSandbox } from "./claudeSandbox.js";
24
24
  import { brandedEnvironmentName, RUNTIME_BRAND } from "./runtimeBrand.js";
25
- import { IMPEL_CODEX_PARENT_DELEGATION_INSTRUCTIONS } from "./commands/launch.js";
26
25
 
27
26
  export const CLAUDE_CONFIG_ID = "1ced0000-0000-4000-8000-000000000001";
28
27
  const CHATGPT_CONFIG_START = `# >>> ${RUNTIME_BRAND.cli.command} app managed gateway >>>`;
@@ -275,9 +274,7 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
275
274
  // unsupported-selection "Reset to default" treatment.
276
275
  // 27: register the separate tenant-bound Tasks MCP server in managed Claude
277
276
  // and ChatGPT/Codex profiles without changing the specialist MCP server.
278
- // 28: add managed ChatGPT parent delegation instructions, including custom-agent
279
- // verbatim relay opt-in and directAnswer one-shot MCP answer routing.
280
- export const CURRENT_CONFIG_VERSION = 28;
277
+ export const CURRENT_CONFIG_VERSION = 27;
281
278
 
282
279
  // Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
283
280
  // helper rebranding, and signing. A vendored bundle is rebuilt only when this
@@ -1348,9 +1345,6 @@ function writeChatGPTConfig(
1348
1345
  `model_catalog_json = ${tomlString(paths.chatgpt.catalog)}`,
1349
1346
  ...(selectedEffort ? [`model_reasoning_effort = ${tomlString(selectedEffort)}`] : []),
1350
1347
  ...(selectedTier ? [`service_tier = ${tomlString(selectedTier)}`] : []),
1351
- ...(RUNTIME_BRAND.features.agents
1352
- ? [`developer_instructions = ${tomlString(IMPEL_CODEX_PARENT_DELEGATION_INSTRUCTIONS)}`]
1353
- : []),
1354
1348
  "",
1355
1349
  // The built-in ChatGPT provider derives its inference endpoint from
1356
1350
  // chatgpt.com even when chatgpt_base_url points at the gateway. Keep the
@@ -14,7 +14,6 @@ import {
14
14
  resolveDefaultGateway,
15
15
  saveConfig,
16
16
  } from "../config.js";
17
- import { parentVerbatimRelayAppendix } from "../verbatimRelay.js";
18
17
  import { impelClaudeBaseUrl, impelCrossAppClaudeBaseUrl } from "../claudeSetup.js";
19
18
  import { withGitEnvironment } from "../skills.js";
20
19
  import { assertProviderScopes, ensureTenantSelection, tenantCredential } from "../tenants.js";
@@ -50,18 +49,10 @@ export {
50
49
 
51
50
  export const IMPEL_SPECIALIST_DELEGATION_INSTRUCTIONS = `You are running in an Impel tenant-scoped session. Before starting any non-trivial task, call the Impel MCP tool list_specialists. If exactly one available specialist clearly matches the user's request, its capabilities and its exclusions, delegate the complete request by calling start_specialist_run exactly once with a stable idempotency key, then call read_specialist_run until it reaches a terminal state. When the run succeeds, use the specialist's result as your response instead of redoing the work. If no specialist clearly matches, the tools are unavailable, or the run fails, continue normally yourself. Do not delegate trivial requests, do not call a specialist excluded from the request, and never invent a specialist result.`;
52
51
 
53
- export const IMPEL_CUSTOM_AGENT_VERBATIM_RELAY_APPENDIX = parentVerbatimRelayAppendix();
54
-
55
- export const IMPEL_CLAUDE_PARENT_DELEGATION_INSTRUCTIONS =
56
- `${IMPEL_SPECIALIST_DELEGATION_INSTRUCTIONS} ${IMPEL_CUSTOM_AGENT_VERBATIM_RELAY_APPENDIX}`;
57
-
58
52
  export const IMPEL_CODEX_SPECIALIST_DELEGATION_INSTRUCTIONS =
59
53
  `${IMPEL_SPECIALIST_DELEGATION_INSTRUCTIONS} ` +
60
54
  `Codex can defer MCP tools behind tool_search. If an Impel specialist tool is not directly visible, call tool_search for its exact name before treating it as unavailable: impel_specialists-list_specialists for discovery, impel_specialists-start_specialist_run to delegate, and impel_specialists-read_specialist_run to poll the result. Use the returned tool for the same one-run delegation flow.`;
61
55
 
62
- export const IMPEL_CODEX_PARENT_DELEGATION_INSTRUCTIONS =
63
- `${IMPEL_CODEX_SPECIALIST_DELEGATION_INSTRUCTIONS} ${IMPEL_CUSTOM_AGENT_VERBATIM_RELAY_APPENDIX}`;
64
-
65
56
  const IMPEL_CODEX_RUNTIME_OVERRIDES = [
66
57
  // Codex models may select code mode even when the standalone
67
58
  // `codex-code-mode-host` companion is not present in the vendor install.
@@ -73,14 +64,14 @@ const IMPEL_CODEX_RUNTIME_OVERRIDES = [
73
64
  export function impelLaunchArguments(tool, argv) {
74
65
  if (!RUNTIME_BRAND.features.agents) return [...argv];
75
66
  if (tool === "claude") {
76
- return ["--append-system-prompt", IMPEL_CLAUDE_PARENT_DELEGATION_INSTRUCTIONS, ...argv];
67
+ return ["--append-system-prompt", IMPEL_SPECIALIST_DELEGATION_INSTRUCTIONS, ...argv];
77
68
  }
78
69
  if (tool === "codex") {
79
70
  // `-c` is a Codex global option, so it must precede subcommands such as
80
71
  // `exec`, `resume`, and `mcp`. JSON strings are valid TOML basic strings.
81
72
  return [
82
73
  "-c",
83
- `developer_instructions=${JSON.stringify(IMPEL_CODEX_PARENT_DELEGATION_INSTRUCTIONS)}`,
74
+ `developer_instructions=${JSON.stringify(IMPEL_CODEX_SPECIALIST_DELEGATION_INSTRUCTIONS)}`,
84
75
  ...IMPEL_CODEX_RUNTIME_OVERRIDES.flatMap((override) => ["-c", override]),
85
76
  ...argv,
86
77
  ];
@@ -91,7 +91,11 @@ export function impelTasksMcpInvocation(tenantId, options = {}) {
91
91
  export function isImpelTasksMcpInvocation(value) {
92
92
  if (!value || typeof value !== "object" || !Array.isArray(value.args)) return false;
93
93
  const suffix = value.args.slice(-5);
94
- return value.type === "stdio"
94
+ // Claude Desktop persists stdio servers without the optional `type` field.
95
+ // Accept that vendor-normalized form so the next Impel update can still
96
+ // recognize, re-pin, and repair its own tenant-bound entry. Any explicit
97
+ // non-stdio type remains foreign and fails closed.
98
+ return (value.type === undefined || value.type === "stdio")
95
99
  && value.env?.[IMPEL_MANAGED_MCP_ENV] === "1"
96
100
  && suffix[0] === "mcp"
97
101
  && suffix[1] === "--target"
@@ -1,59 +0,0 @@
1
- /**
2
- * Catalog opt-in for Impel MCP `answer_native_agent`.
3
- *
4
- * When `directAnswer` is true, managed Claude/Codex adapters call that one-shot
5
- * tool (Impel auth → Eve `/eve/v1/answer`) and return forUser/answer instead of
6
- * list → start → poll. Distinct from agent-internal retrieve shortcuts.
7
- *
8
- * Does not replace the verbatimRelay description / fork_turns override: when an
9
- * agent also sets verbatimRelay, keep that marker and spawn guidance so parents
10
- * still use fork_turns="none" and relay the adapter result verbatim.
11
- */
12
-
13
- import {
14
- VERBATIM_FINAL_TEXT_CONSTRAINTS,
15
- } from "./verbatimRelay.js";
16
-
17
- export function usesDirectAnswer(agent) {
18
- return agent?.directAnswer === true;
19
- }
20
-
21
- export function extractAnswerFinalText(payload) {
22
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
23
- for (const key of ["forUser", "answer"]) {
24
- const text = payload[key];
25
- if (typeof text === "string" && text.trim()) return text;
26
- }
27
- return null;
28
- }
29
-
30
- export function claudeAnswerHardCompletionGuidance() {
31
- return (
32
- `When it succeeds, return forUser if present, otherwise answer, exactly with ${VERBATIM_FINAL_TEXT_CONSTRAINTS}. ` +
33
- "When it fails, return the tool error without inventing a replacement result. " +
34
- "Never invent or independently synthesize a replacement result."
35
- );
36
- }
37
-
38
- export function claudeAnswerSoftCompletionGuidance() {
39
- return (
40
- "When it succeeds, return forUser if present, otherwise answer, faithfully as the answer. " +
41
- "When it fails, return the tool error without inventing a replacement result. " +
42
- "Never invent or independently synthesize a replacement result."
43
- );
44
- }
45
-
46
- export function adapterAnswerHardCompletionGuidance() {
47
- return (
48
- `After the orchestration completes, return its single text output verbatim with ${VERBATIM_FINAL_TEXT_CONSTRAINTS}. ` +
49
- "A successful output is forUser (else answer) exactly. A failure output preserves the error payload."
50
- );
51
- }
52
-
53
- export function adapterAnswerSoftCompletionGuidance() {
54
- return (
55
- "After the orchestration completes, return its single text output as the answer. " +
56
- "A successful output is forUser (else answer). A failure output preserves the error payload. " +
57
- "Never invent or independently synthesize a replacement result."
58
- );
59
- }
@@ -1,64 +0,0 @@
1
- export const VERBATIM_RELAY_OPT_IN_MARKER = "verbatimRelay enabled";
2
-
3
- export const VERBATIM_SPAWN_REQUIREMENT = 'fork_turns="none"';
4
-
5
- export const VERBATIM_FINAL_TEXT_CONSTRAINTS =
6
- "no preface, rewriting, Markdown changes, or independent synthesis";
7
-
8
- export function usesVerbatimRelay(agent) {
9
- return agent?.verbatimRelay === true;
10
- }
11
-
12
- export function parentVerbatimRelayAppendix() {
13
- return (
14
- `When an explicit custom agent's catalog-derived description declares ${VERBATIM_RELAY_OPT_IN_MARKER}, ` +
15
- `spawn it with ${VERBATIM_SPAWN_REQUIREMENT} and relay its finalText verbatim with ${VERBATIM_FINAL_TEXT_CONSTRAINTS}; ` +
16
- "preserve Sources sections and citations exactly. " +
17
- "Custom agents without that declaration keep the default delegation behavior."
18
- );
19
- }
20
-
21
- export function customAgentVerbatimDescriptionLead() {
22
- return (
23
- `Explicit custom agent with ${VERBATIM_RELAY_OPT_IN_MARKER}: ` +
24
- `callers must use ${VERBATIM_SPAWN_REQUIREMENT} and relay its result verbatim`
25
- );
26
- }
27
-
28
- export function adapterCallerSpawnGuidance(clientLabel) {
29
- return (
30
- `Callers must spawn this explicit custom ${clientLabel} agent with ${VERBATIM_SPAWN_REQUIREMENT} ` +
31
- "and must relay your result verbatim; this is caller guidance and cannot enforce host spawn behavior."
32
- );
33
- }
34
-
35
- export function adapterHardCompletionGuidance() {
36
- return (
37
- `After the orchestration completes, return its single text output verbatim with ${VERBATIM_FINAL_TEXT_CONSTRAINTS}. ` +
38
- "A successful output is result.finalText exactly. A failure output preserves the durable runId, output, and error."
39
- );
40
- }
41
-
42
- export function adapterSoftCompletionGuidance() {
43
- return (
44
- "After the orchestration completes, return its single text output as the answer. " +
45
- "A successful output is result.finalText. A failure output preserves the durable runId, output, and error. " +
46
- "Never invent or independently synthesize a replacement result."
47
- );
48
- }
49
-
50
- export function claudeHardCompletionGuidance() {
51
- return (
52
- `When it succeeds, return result.finalText exactly with ${VERBATIM_FINAL_TEXT_CONSTRAINTS}. ` +
53
- "When it fails, return the durable runId, preserved output, and error. " +
54
- "Never invent or independently synthesize a replacement result."
55
- );
56
- }
57
-
58
- export function claudeSoftCompletionGuidance() {
59
- return (
60
- "When it succeeds, return result.finalText faithfully as the answer. " +
61
- "When it fails, return the durable runId, preserved output, and error. " +
62
- "Never invent or independently synthesize a replacement result."
63
- );
64
- }