@jini-ai/daemon 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/agent-executor/contracts.d.ts +45 -14
  3. package/dist/agent-executor/contracts.js +0 -9
  4. package/dist/agent-executor/event-translation.d.ts +2 -2
  5. package/dist/agent-executor/event-translation.js +0 -2
  6. package/dist/agent-executor/events.d.ts +26 -6
  7. package/dist/agent-executor/events.js +198 -11
  8. package/dist/agent-executor/handoff.d.ts +1 -1
  9. package/dist/agent-executor/handoff.js +0 -1
  10. package/dist/agent-executor/index.d.ts +1 -1
  11. package/dist/agent-executor/launch.d.ts +1 -1
  12. package/dist/agent-executor/launch.js +47 -3
  13. package/dist/agent-executor/mcp-bridge.d.ts +2 -2
  14. package/dist/agent-executor/mcp-bridge.js +19 -8
  15. package/dist/agent-executor/model-receipts.d.ts +6 -0
  16. package/dist/agent-executor/model-receipts.js +26 -0
  17. package/dist/agent-executor.d.ts +7 -1665
  18. package/dist/agent-executor.js +7 -3007
  19. package/dist/core/read-only-tools.d.ts +130 -0
  20. package/dist/core/read-only-tools.js +115 -0
  21. package/dist/core/tool-audit.d.ts +131 -0
  22. package/dist/core/tool-audit.js +113 -0
  23. package/dist/http/active-context.d.ts +2 -2
  24. package/dist/http/agents.d.ts +8 -2
  25. package/dist/http/agents.js +6 -2
  26. package/dist/http/component-catalog.d.ts +2 -2
  27. package/dist/http/connectors.d.ts +17 -17
  28. package/dist/http/daemon-status.d.ts +2 -2
  29. package/dist/http/db-ops.d.ts +3 -3
  30. package/dist/http/delegated-tools.d.ts +1 -1
  31. package/dist/http/delegated-tools.js +1 -1
  32. package/dist/http/frontend-sessions.d.ts +1 -1
  33. package/dist/http/health.d.ts +6 -6
  34. package/dist/http/host-tools.d.ts +2 -2
  35. package/dist/http/index.d.ts +2 -2
  36. package/dist/http/index.js +1 -1
  37. package/dist/http/media.d.ts +4 -4
  38. package/dist/http/memory.d.ts +15 -15
  39. package/dist/http/remote-run-events.d.ts +2 -2
  40. package/dist/http/research.d.ts +1 -1
  41. package/dist/http/route-manifest.js +2 -2
  42. package/dist/http/routines.d.ts +7 -7
  43. package/dist/http/run-credentials.d.ts +2 -2
  44. package/dist/http/run-credentials.js +2 -2
  45. package/dist/http/runs.d.ts +31 -6
  46. package/dist/http/runs.js +36 -1
  47. package/dist/http/terminals.d.ts +6 -6
  48. package/dist/http/tool-catalog.d.ts +2 -2
  49. package/dist/http/xai.d.ts +6 -6
  50. package/dist/index.d.ts +75 -571
  51. package/dist/index.js +75 -571
  52. package/dist/read-only-tools.d.ts +1 -129
  53. package/dist/read-only-tools.js +2 -114
  54. package/dist/run-credentials.d.ts +47 -23
  55. package/dist/run-credentials.js +24 -8
  56. package/dist/scheduler.d.ts +5 -0
  57. package/dist/scheduler.js +13 -1
  58. package/dist/session-coordination.d.ts +58 -24
  59. package/dist/session-coordination.js +40 -12
  60. package/dist/store/event-log/sqlite.js +3 -3
  61. package/dist/surface-exchanges.d.ts +207 -61
  62. package/dist/surface-exchanges.js +167 -20
  63. package/dist/tool-audit.d.ts +1 -113
  64. package/dist/tool-audit.js +2 -94
  65. package/dist/tool-recovery/diagnostics.d.ts +132 -0
  66. package/dist/tool-recovery/diagnostics.js +110 -0
  67. package/dist/tool-recovery/federated-refusal.d.ts +92 -0
  68. package/dist/tool-recovery/federated-refusal.js +152 -0
  69. package/dist/tool-recovery/index.d.ts +6 -0
  70. package/dist/tool-recovery/index.js +6 -0
  71. package/dist/tool-recovery/recovery.d.ts +49 -0
  72. package/dist/tool-recovery/recovery.js +419 -0
  73. package/dist/tool-recovery/redaction.d.ts +166 -0
  74. package/dist/tool-recovery/redaction.js +192 -0
  75. package/dist/tool-recovery/stack.d.ts +69 -0
  76. package/dist/tool-recovery/stack.js +62 -0
  77. package/package.json +35 -12
@@ -2,7 +2,7 @@ import { spawn as nodeSpawn, } from 'node:child_process';
2
2
  import { promises as fsPromises, } from 'node:fs';
3
3
  import { tmpdir, } from 'node:os';
4
4
  import { join, } from 'node:path';
5
- import { agentCapabilities, applyAgentLaunchEnv, ensureAgentCapabilities, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
5
+ import { agentCapabilities, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
6
6
  import { collectProcessTreePids, createCommandInvocation, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform';
7
7
  import { applyImagePromptDelivery, } from '../image-prompt-delivery.js';
8
8
  import { SUPPORTED_STREAM_FORMATS, AgentExecutorError, } from './contracts.js';
@@ -640,6 +640,18 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
640
640
  await lifecycle.finish({ runId, status: 'failed', code: null, signal: null, resumable: false });
641
641
  throw new AgentExecutorError({ code: code, message: message });
642
642
  }
643
+ /**
644
+ * Every run with a live child, keyed by run id, mapped to how a mid-run message reaches it — the
645
+ * stdin handle's `sendUserMessage` for a stdin-driven run, a constant `'unsupported'` for an
646
+ * ACP/pi run (whose RPC takes one prompt per turn). Removed on the child's `'close'`, so a
647
+ * finished or unknown run reads as `'not-running'`. Bounded by the number of live children.
648
+ */
649
+ const liveRunInputs = new Map();
650
+ /** `AgentExecutor.sendUserMessage()` — see that interface method's own doc. @complexity O(1). */
651
+ async function sendUserMessage({ runId, text }) {
652
+ const deliver = liveRunInputs.get(runId);
653
+ return deliver ? deliver(text) : 'not-running';
654
+ }
643
655
  /**
644
656
  * `AgentExecutor.run()` — see that interface method's own doc for the
645
657
  * public contract. Implementation note on shape: every guard below
@@ -706,6 +718,23 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
706
718
  const resolvedEnv = { ...(def.env ?? {}), ...resolveRunEnv({ input: input, hostEnv: process.env }) };
707
719
  const launch = await resolveLaunch({ input: { runId: input.runId, def, resolvedEnv }, deps: { resolveAgentLaunch: resolveAgentLaunchFn, failBeforeSpawn } });
708
720
  const spawnEnv = applyAgentLaunchEnvFn({ env: { ...resolvedEnv }, launch });
721
+ // Resolve from the launch environment before staging or sending any prompt. Pin the same
722
+ // concrete ID that the picker displays; an opaque native route cannot silently start a run.
723
+ let modelCatalog;
724
+ try {
725
+ const selection = await (options.resolveModelForLaunch ?? resolveModelForLaunch)({ def, context: {
726
+ executable: launch.launchPath, cwd: input.cwd, env: spawnEnv,
727
+ ...(input.model && input.model !== 'default' ? { model: input.model } : {}),
728
+ ...(input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {}),
729
+ ...(input.settingSources ? { settingSources: input.settingSources } : {}),
730
+ ...(input.settings ? { settings: input.settings } : {}),
731
+ } });
732
+ input.model = selection.model;
733
+ modelCatalog = selection.catalog;
734
+ }
735
+ catch {
736
+ return failBeforeSpawn({ runId: input.runId, code: 'AGENT_MODEL_UNRESOLVED', message: 'AgentExecutor: pick a concrete model; the starting model could not be resolved for this launch.' });
737
+ }
709
738
  // Fill the def's `--help` capability gate before `buildArgs` reads it. Without this, only a
710
739
  // host that happened to call `detectAgents` in THIS process ever had the gate filled, so e.g.
711
740
  // `claude` never got `--include-partial-messages` (no streamed text). Probes once per binary;
@@ -898,6 +927,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
898
927
  const formatForStdin = { streamFormat };
899
928
  const stdinHandle = isStdinDrivenFormat(formatForStdin)
900
929
  ? wireChildLifecycle({
930
+ startingModel: input.model,
931
+ modelCatalog,
901
932
  runId: input.runId,
902
933
  def,
903
934
  streamFormat: formatForStdin.streamFormat,
@@ -915,7 +946,18 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
915
946
  expectsJiniBridge: writtenMcpJsonPath !== undefined,
916
947
  })
917
948
  : null;
918
- await confirmChildSpawned({ input: { runId: input.runId, def, child }, deps: { releaseStagedResources, failBeforeSpawn } });
949
+ // Registered with the lifecycle wiring above, before the spawn-confirmation await, so a child
950
+ // that closes immediately never leaves a stale entry behind.
951
+ const runId = input.runId;
952
+ liveRunInputs.set(runId, stdinHandle ? stdinHandle.sendUserMessage : async () => 'unsupported');
953
+ child.once('close', () => liveRunInputs.delete(runId));
954
+ try {
955
+ await confirmChildSpawned({ input: { runId: input.runId, def, child }, deps: { releaseStagedResources, failBeforeSpawn } });
956
+ }
957
+ catch (error) {
958
+ liveRunInputs.delete(runId);
959
+ throw error;
960
+ }
919
961
  // Now — and only now — is there a live process that could consume the locked side effect, so
920
962
  // this is where a def's handoff watcher starts. Deliberately not awaited: the whole point is to
921
963
  // release the lock as soon as the child confirms the handoff, in parallel with this run
@@ -937,6 +979,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
937
979
  prompt: `${overlayDelivery.promptPrefix}${input.prompt}`,
938
980
  cwd: input.cwd,
939
981
  model: input.model,
982
+ modelCatalog,
940
983
  imagePaths: input.imagePaths ?? [],
941
984
  envFormat: def.acpMcpEnvFormat,
942
985
  mcpBridge,
@@ -967,6 +1010,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
967
1010
  prompt: `${overlayDelivery.promptPrefix}${input.prompt}`,
968
1011
  cwd: input.cwd,
969
1012
  model: input.model,
1013
+ modelCatalog,
970
1014
  imagePaths: input.imagePaths ?? [],
971
1015
  uploadRoot: input.uploadRoot,
972
1016
  }, deps: {
@@ -998,6 +1042,6 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
998
1042
  stdinHandle.imageContents = input.imageContents;
999
1043
  writePromptToStdin(def, child, stdinPrompt, stdinHandle);
1000
1044
  }
1001
- return { run };
1045
+ return { run, sendUserMessage };
1002
1046
  }
1003
1047
  //# sourceMappingURL=launch.js.map
@@ -1,5 +1,5 @@
1
- import { type AcpMcpServerInput, type RuntimeAgentDef } from '@jini-ai/agent-runtime';
2
- import { type McpJsonInjectionOptions, type McpJsonServerEntry, type McpBridgeDelivery, type PreparedCodexHome, type PreparedClaudeConfigDir, type ClaudeConfigDirIsolationOptions, type FailBeforeSpawn } from './contracts.js';
1
+ import type { AcpMcpServerInput, RuntimeAgentDef } from '@jini-ai/agent-runtime';
2
+ import type { McpJsonInjectionOptions, McpJsonServerEntry, McpBridgeDelivery, PreparedCodexHome, PreparedClaudeConfigDir, ClaudeConfigDirIsolationOptions, FailBeforeSpawn } from './contracts.js';
3
3
  /** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
4
4
  export declare const MCP_BRIDGE_UNAVAILABLE = "MCP_BRIDGE_UNAVAILABLE";
5
5
  /**
@@ -1,9 +1,7 @@
1
1
  import { promises as fsPromises, } from 'node:fs';
2
2
  import { homedir, tmpdir, } from 'node:os';
3
- import { join, } from 'node:path';
4
- import {} from '@jini-ai/agent-runtime';
3
+ import { join, resolve, } from 'node:path';
5
4
  import { isRecord, errorMessage, } from './values.js';
6
- import {} from './contracts.js';
7
5
  const JINI_MCP_SERVER_KEY = 'jini';
8
6
  /** The run-error code a run gets when its CLI reports the injected `jini` bridge as anything but connected. */
9
7
  export const MCP_BRIDGE_UNAVAILABLE = 'MCP_BRIDGE_UNAVAILABLE';
@@ -554,12 +552,23 @@ function defaultMkdtempCodexHome({ prefix }) {
554
552
  function defaultRemoveCodexHomeDir({ path }) {
555
553
  return fsPromises.rm(path, { recursive: true, force: true });
556
554
  }
555
+ /** Config credentials stay run-scoped, but rollouts must survive both completion and cancellation.
556
+ * Native directory links keep Codex as the sole writer of its own persisted session format.
557
+ * Cleanup removes the links themselves, never their targets. Two fixed directories, O(1) I/O. */
558
+ async function defaultLinkCodexSessionStore({ runHome, sourceHome }, _optional = {}) {
559
+ for (const name of ['sessions', 'archived_sessions']) {
560
+ const target = resolve(sourceHome, name);
561
+ await fsPromises.mkdir(target, { recursive: true, mode: 0o700 });
562
+ await fsPromises.symlink(target, join(runHome, name), 'junction');
563
+ }
564
+ }
557
565
  function resolveCodexHomeSeams(options) {
558
566
  return {
559
567
  mkdtemp: options.mkdtemp ?? defaultMkdtempCodexHome,
560
568
  readFile: options.readFile ?? defaultReadMcpJsonFile,
561
569
  writeFile: options.writeFile ?? defaultWriteMcpJsonFile,
562
570
  removeDir: options.removeDir ?? defaultRemoveCodexHomeDir,
571
+ linkSessionStore: options.linkSessionStore ?? defaultLinkCodexSessionStore,
563
572
  };
564
573
  }
565
574
  /**
@@ -575,8 +584,9 @@ function resolveCodexHomeSeams(options) {
575
584
  * 0.151.0) to fail fast with a structured `401 Unauthorized` stream event, never an interactive
576
585
  * login prompt or a hang — see `defs/codex.ts`'s module doc for the full transcript summary.
577
586
  *
578
- * **Never touches the real `CODEX_HOME`.** `sourceCodexHomeDir` is read-only throughout; nothing is
579
- * ever written back to it.
587
+ * **Never changes the real config or credentials.** Those source files stay read-only. Only the
588
+ * native rollout directories are shared: deleting a run's scratch home must not erase the thread
589
+ * whose id the host already persisted. No copied login or run-bound MCP credential is retained.
580
590
  *
581
591
  * A failure after the directory is created (a rejecting `writeFile`, most plausibly) does not leak
582
592
  * it: the directory may already hold a partial `config.toml` or a copied credential, so the
@@ -586,9 +596,9 @@ function resolveCodexHomeSeams(options) {
586
596
  * `@jini-ai/agent-runtime`'s `prepareAgentLogFile`'s `label` is.
587
597
  * @param entry - The shared bridge entry.
588
598
  * @param sourceCodexHomeDir - Where to read the real install's `config.toml`/`auth.json` from — see {@link resolveSourceCodexHomeDir}.
589
- * @param seams - Injectable mkdtemp/readFile/writeFile/removeDir, real filesystem by default.
590
- * @throws Whatever `mkdtemp`/`writeFile` rejects with — the caller ({@link prepareCodexHomeIfNeeded}) turns that into a pre-spawn `AGENT_SPAWN_FAILED` failure, matching {@link writeMcpJsonForRun}'s own contract.
591
- * @complexity O(1) plus one directory creation and up to two best-effort file read/write round trips.
599
+ * @param seams - Injectable mkdtemp/readFile/writeFile/removeDir/linkSessionStore, real filesystem by default.
600
+ * @throws Whatever staging rejects with — the caller ({@link prepareCodexHomeIfNeeded}) turns that into a pre-spawn `AGENT_SPAWN_FAILED` failure, matching {@link writeMcpJsonForRun}'s own contract.
601
+ * @complexity O(1): fixed directory links and up to two best-effort file read/write round trips.
592
602
  * @overallScore 100/100
593
603
  */
594
604
  async function prepareCodexHomeForRun(runId, entry, sourceCodexHomeDir, seams) {
@@ -618,6 +628,7 @@ async function prepareCodexHomeForRun(runId, entry, sourceCodexHomeDir, seams) {
618
628
  // No stored login (or unreadable) — the spawned CLI runs unauthenticated. Confirmed above:
619
629
  // this fails the run fast and observably, never as a hang.
620
630
  }
631
+ await seams.linkSessionStore({ runHome: dir, sourceHome: sourceCodexHomeDir }, {});
621
632
  }
622
633
  catch (err) {
623
634
  await seams.removeDir({ path: dir }).catch(() => {
@@ -0,0 +1,6 @@
1
+ import type { RunAgentPayload } from '@jini-ai/protocol';
2
+ import { type DiscoveredModel } from '@jini-ai/agent-runtime';
3
+ /** Status metadata comes from CLI/RPC frames, never generated text. Dedupe repeats and record
4
+ * each observed transition separately so durable run events can describe model switches. */
5
+ export declare function createModelReceiptTracker(startingModel?: string, models?: readonly DiscoveredModel[]): (payload: RunAgentPayload) => RunAgentPayload | null;
6
+ //# sourceMappingURL=model-receipts.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { modelIdentityKind } from '@jini-ai/agent-runtime';
2
+ /** Status metadata comes from CLI/RPC frames, never generated text. Dedupe repeats and record
3
+ * each observed transition separately so durable run events can describe model switches. */
4
+ export function createModelReceiptTracker(startingModel, models = []) {
5
+ // The catalog is launch-scoped evidence. Normalize both the pinned and reported names
6
+ // so a CLI's alias/display spelling does not fabricate a durable model transition.
7
+ const normalize = (id) => {
8
+ const row = models.find(model => model.id === id) || models.find(model => model.label === id);
9
+ return row?.resolvedId || row?.id || id;
10
+ };
11
+ let current = startingModel && normalize(startingModel);
12
+ return (payload) => {
13
+ if (payload.type !== 'status' || !payload.model || typeof payload.model !== 'string')
14
+ return null;
15
+ const observed = normalize(payload.model);
16
+ if (modelIdentityKind(observed) !== 'concrete')
17
+ return null;
18
+ if (observed === current)
19
+ return null;
20
+ const prior = current;
21
+ current = observed;
22
+ return { type: 'status', label: prior ? 'model_switch' : 'observed_model', model: current,
23
+ ...(prior ? { previousModel: prior, detail: `Previous model: ${prior}` } : {}) };
24
+ };
25
+ }
26
+ //# sourceMappingURL=model-receipts.js.map