@agentdocumentformat/cli 0.8.0 → 0.8.2

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 (34) hide show
  1. package/dist/adf.mjs +6 -6
  2. package/dist/chunks/{App-A5GUFCFJ.mjs → App-4AZZJNN2.mjs} +511 -406
  3. package/dist/chunks/{adf-schema-YJSGLMA4.mjs → adf-schema-AIPBQTR2.mjs} +9 -3
  4. package/dist/chunks/{chunk-AZYOXXVL.mjs → chunk-2Z7X3UUR.mjs} +11 -198
  5. package/dist/chunks/{chunk-4OGZFUKA.mjs → chunk-7JZXMDDF.mjs} +33 -1
  6. package/dist/chunks/{chunk-V2T6NZX2.mjs → chunk-AEAJDCVK.mjs} +1 -1
  7. package/dist/chunks/{chunk-NZO5O4VN.mjs → chunk-BR3AD5QZ.mjs} +7 -1
  8. package/dist/chunks/{chunk-WVTPPOKK.mjs → chunk-BS3USVEA.mjs} +14 -3
  9. package/dist/chunks/{chunk-JVPZIMXW.mjs → chunk-D7A77SNL.mjs} +1 -1
  10. package/dist/chunks/{chunk-LCMANRYY.mjs → chunk-E7LC6SHP.mjs} +1 -1
  11. package/dist/chunks/{chunk-LGL6ML37.mjs → chunk-F3WSCAJD.mjs} +1 -1
  12. package/dist/chunks/{chunk-6XQZGYMK.mjs → chunk-IMVMSU3A.mjs} +6 -2
  13. package/dist/chunks/{chunk-C4GA5SI4.mjs → chunk-KCIRFUZH.mjs} +2 -2
  14. package/dist/chunks/{chunk-YRMKSHW3.mjs → chunk-LVF2DRBR.mjs} +25 -12
  15. package/dist/chunks/{chunk-PRPTKMQW.mjs → chunk-RHBGCOS3.mjs} +2 -2
  16. package/dist/chunks/{chunk-WX6QCUWI.mjs → chunk-RJDOVMM4.mjs} +95 -12
  17. package/dist/chunks/{chunk-G6NBFYG5.mjs → chunk-SOSPQPEN.mjs} +189 -0
  18. package/dist/chunks/chunk-VENQBMOA.mjs +62 -0
  19. package/dist/chunks/{chunk-XT2BJUFC.mjs → chunk-YRBFIDLB.mjs} +33 -6
  20. package/dist/chunks/{claude-subscription-33BMZB6I.mjs → claude-subscription-EB4LAAVV.mjs} +3 -3
  21. package/dist/chunks/{cli-I47IFDHJ.mjs → cli-PCVFB6B4.mjs} +5 -5
  22. package/dist/chunks/{commands-E4EQUHE5.mjs → commands-KOMPXJZS.mjs} +3 -3
  23. package/dist/chunks/{daemon-FQ5BV3AP.mjs → daemon-HEQ3FUYH.mjs} +729 -111
  24. package/dist/chunks/{daemon-control-L3CLG63P.mjs → daemon-control-MKN7G5M3.mjs} +4 -4
  25. package/dist/chunks/{layout-EPJQNFAI.mjs → layout-ECWGJF6M.mjs} +2 -2
  26. package/dist/chunks/{open-TLGS2Z3Q.mjs → open-3OR6WT4T.mjs} +4 -4
  27. package/dist/chunks/{pipeline-executor-FD4FBWQA.mjs → pipeline-executor-DRYEEFDR.mjs} +4 -4
  28. package/dist/chunks/{remote-access-cli-XXAPQWH6.mjs → remote-access-cli-GLEEP7DI.mjs} +2 -2
  29. package/dist/chunks/sdk-OJOAF4DP.mjs +30014 -0
  30. package/dist/chunks/{store-ZRN2WCFB.mjs → store-55CQSAOX.mjs} +3 -2
  31. package/dist/chunks/{tui-SPSPDADM.mjs → tui-RAUVFOYY.mjs} +11 -11
  32. package/package.json +2 -2
  33. package/dist/chunks/sdk-5FD2ZP7N.mjs +0 -28422
  34. package/dist/chunks/{chunk-APRU6PHC.mjs → chunk-JZ7AQNPM.mjs} +3 -3
@@ -32,11 +32,6 @@ import {
32
32
  resolveCompactThreshold,
33
33
  resolveLoopThreshold
34
34
  } from "./chunk-HBOFZRUS.mjs";
35
- import {
36
- formatTurnErrorRow,
37
- parseLoopToDisplay,
38
- providerContextCategory
39
- } from "./chunk-4OGZFUKA.mjs";
40
35
  import {
41
36
  BUNDLED_REGISTRY_UPDATED_AT,
42
37
  MCP_REGISTRY,
@@ -46,10 +41,11 @@ import {
46
41
  deriveRegistrationTestPlan,
47
42
  isRegistrationAgentVisible,
48
43
  isSensitiveMcpHeader,
44
+ isSubscriptionType,
49
45
  mcpCredentialRef,
50
46
  parseMcpRegistryDocument,
51
47
  pinServerConfigToRegistration
52
- } from "./chunk-G6NBFYG5.mjs";
48
+ } from "./chunk-SOSPQPEN.mjs";
53
49
  import {
54
50
  TailscaleClient,
55
51
  defaultExec,
@@ -61,16 +57,22 @@ import {
61
57
  import {
62
58
  wrapper_default
63
59
  } from "./chunk-F5ELW46V.mjs";
60
+ import {
61
+ formatOwnerCommandRow,
62
+ formatTurnErrorRow,
63
+ parseLoopToDisplay,
64
+ providerContextCategory
65
+ } from "./chunk-7JZXMDDF.mjs";
64
66
  import {
65
67
  AiSdkProvider,
66
68
  createClaudeSubscriptionProvider,
67
69
  getTokenCounterService,
68
70
  seedMandatoryReasoningModels,
69
71
  setMandatoryReasoningPersister
70
- } from "./chunk-C4GA5SI4.mjs";
72
+ } from "./chunk-KCIRFUZH.mjs";
71
73
  import {
72
74
  executeNode
73
- } from "./chunk-LGL6ML37.mjs";
75
+ } from "./chunk-F3WSCAJD.mjs";
74
76
  import {
75
77
  ParseError,
76
78
  parse
@@ -138,7 +140,7 @@ import {
138
140
  withLoop,
139
141
  withSource,
140
142
  withTurn
141
- } from "./chunk-YRMKSHW3.mjs";
143
+ } from "./chunk-LVF2DRBR.mjs";
142
144
  import {
143
145
  DEFAULT_BASE_PROMPT,
144
146
  DEFAULT_COMPACTION_PROMPT,
@@ -147,7 +149,7 @@ import {
147
149
  DOCS_GUIDES_URL,
148
150
  MIND_PROMPT_SECTION,
149
151
  SOUL_PROMPT_SECTION
150
- } from "./chunk-LCMANRYY.mjs";
152
+ } from "./chunk-E7LC6SHP.mjs";
151
153
  import "./chunk-NZ7JTHEO.mjs";
152
154
  import {
153
155
  nanoid
@@ -280,7 +282,7 @@ import {
280
282
  pidAlive,
281
283
  processAgentLeases,
282
284
  trustedEndpoint
283
- } from "./chunk-JVPZIMXW.mjs";
285
+ } from "./chunk-D7A77SNL.mjs";
284
286
  import {
285
287
  NETWORK_PROBLEM_TEXT
286
288
  } from "./chunk-3UGZFWIF.mjs";
@@ -301,7 +303,7 @@ import {
301
303
  noteLocalProofProblem,
302
304
  probeKeychain,
303
305
  testSeamsAllowed
304
- } from "./chunk-WVTPPOKK.mjs";
306
+ } from "./chunk-BS3USVEA.mjs";
305
307
  import {
306
308
  ensureDaemonToken,
307
309
  tokensEqual
@@ -325,9 +327,12 @@ import {
325
327
  LOOP_NAME_PATTERN,
326
328
  LOOP_NAME_RULE,
327
329
  LOOP_TOOLS_MAX,
328
- LoopConfigSchema,
329
- MAX_SIDE_LOOPS
330
- } from "./chunk-XT2BJUFC.mjs";
330
+ LoopConfigSchema
331
+ } from "./chunk-YRBFIDLB.mjs";
332
+ import {
333
+ parseLambdaRef,
334
+ splitArgv
335
+ } from "./chunk-VENQBMOA.mjs";
331
336
  import {
332
337
  AGENT_DEFAULTS,
333
338
  CODE_EXECUTION_DEFAULTS,
@@ -335,13 +340,15 @@ import {
335
340
  DEFAULT_NEW_LOOP_TOOLS,
336
341
  DEFAULT_TOOLS,
337
342
  FILE_PROTECTION_LEVELS,
343
+ MAX_SIDE_LOOPS,
338
344
  META_PROTECTION_LEVELS,
339
345
  RECOVERY_DEFAULTS,
340
346
  RESERVED_AGENT_PATH_SEGMENTS,
341
347
  SETTABLE_STATES,
342
348
  UPDATABLE_STATES,
349
+ hasLoopGoal,
343
350
  resolveStartInState
344
- } from "./chunk-NZO5O4VN.mjs";
351
+ } from "./chunk-BR3AD5QZ.mjs";
345
352
  import {
346
353
  external_exports
347
354
  } from "./chunk-ZJKXHM4Q.mjs";
@@ -43981,6 +43988,44 @@ import { existsSync as existsSync11, readdirSync as readdirSync5, realpathSync,
43981
43988
  import { readdir as readdir4, realpath as realpath2 } from "node:fs/promises";
43982
43989
  import { basename as basename8, dirname as dirname13, join as join24, resolve as resolve10 } from "node:path";
43983
43990
 
43991
+ // src/main/adf/loop-teardown.ts
43992
+ function dropTimersForLoop(workspace, name15) {
43993
+ let dropped = 0, kept = 0;
43994
+ for (let timer of workspace.getTimers())
43995
+ if ((timer.loop ?? MAIN_LOOP) === name15) {
43996
+ if (timer.locked) {
43997
+ kept++;
43998
+ continue;
43999
+ }
44000
+ workspace.deleteTimer(timer.id), dropped++;
44001
+ }
44002
+ return { dropped, kept };
44003
+ }
44004
+ async function teardownRemovedLoopsOffline(workspace, previous, next) {
44005
+ let kept = new Set((next.loops ?? []).map((l) => l.name)), removed = (previous.loops ?? []).map((l) => l.name).filter((name15) => name15 !== MAIN_LOOP && !kept.has(name15));
44006
+ for (let name15 of removed) {
44007
+ let log2 = (level, event, message) => {
44008
+ try {
44009
+ workspace.insertLog(level, "loop", event, name15, message);
44010
+ } catch {
44011
+ }
44012
+ };
44013
+ try {
44014
+ let { archivedEntries } = await workspace.forLoop(name15).clearLoop({ forceAudit: !0 });
44015
+ log2("info", "loop_torn_down", archivedEntries > 0 ? `Loop "${name15}" removed via config while the agent was stopped; ${archivedEntries} stream ${archivedEntries === 1 ? "entry" : "entries"} archived to adf_audit under "loop:${name15}"` : `Loop "${name15}" removed via config while the agent was stopped; its stream was empty, nothing to archive`);
44016
+ let timers = dropTimersForLoop(workspace, name15);
44017
+ timers.dropped > 0 && log2("info", "loop_timers_dropped", `Dropped ${timers.dropped} timer(s) belonging to deleted loop "${name15}" \u2014 orphaned timers are never re-pointed at main`), timers.kept > 0 && log2("warn", "loop_timers_locked_kept", `Kept ${timers.kept} locked timer(s) belonging to deleted loop "${name15}" \u2014 a lock is human-only and survives the loop; unlock or delete them in Studio`);
44018
+ } catch (error) {
44019
+ console.error(`[LoopTeardown] Teardown of removed loop "${name15}" failed:`, error), log2(
44020
+ "error",
44021
+ "loop_teardown_failed",
44022
+ `Teardown of removed loop "${name15}" failed; its stream may still be in adf_loop: ${error instanceof Error ? error.message : String(error)}`
44023
+ );
44024
+ }
44025
+ }
44026
+ return removed;
44027
+ }
44028
+
43984
44029
  // src/main/adf/apply-default-provider.ts
43985
44030
  function toAdfProviderConfig(p) {
43986
44031
  let adf = {
@@ -58226,6 +58271,7 @@ function expectsNumber(path) {
58226
58271
  return NUMERIC_LEAF_NAMES.has(leaf) || NUMERIC_LEAF_SUFFIX.test(leaf);
58227
58272
  }
58228
58273
  var DECLARATION_ARRAYS = /* @__PURE__ */ new Set([
58274
+ "commands",
58229
58275
  "tools",
58230
58276
  "serving.api",
58231
58277
  "ws_connections",
@@ -58303,8 +58349,10 @@ var InputSchema6 = external_exports.object({
58303
58349
  return this.err(`Invalid input: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
58304
58350
  try {
58305
58351
  let { path, value, index } = parsed.data, action = parsed.data.action ?? "set", segments = this.parsePath(path);
58352
+ if (!isAuthorized && segments[0] === "commands" && segments[segments.length - 1] === "locked" && value === !0)
58353
+ return this.err("Only the owner can lock a command.");
58306
58354
  for (let seg of segments) {
58307
- if (seg === "locked" || seg === "locked_fields")
58355
+ if (seg === "locked" && !(isAuthorized && segments[0] === "commands") || seg === "locked_fields")
58308
58356
  return this.err("Cannot modify locking configuration.");
58309
58357
  if (seg === "restricted" || seg === "restricted_methods")
58310
58358
  return this.err("Cannot modify restriction configuration.");
@@ -58331,11 +58379,11 @@ var InputSchema6 = external_exports.object({
58331
58379
  let lockViolation = this.checkLocks(config, resolved);
58332
58380
  if (lockViolation && !isAuthorized && !isOverride)
58333
58381
  return this.errProtected(lockViolation, this.lockDescription(path, value, action));
58334
- let valErr = this.validateField(path, value, action);
58382
+ let valErr = this.validateField(resolved.join("."), value, action, workspace);
58335
58383
  if (valErr) return this.err(valErr);
58336
- let declErr = this.checkDeclarationIntegrity(config, resolved, value, action);
58384
+ let declErr = this.checkDeclarationIntegrity(config, resolved, value, action, isAuthorized);
58337
58385
  if (declErr) return this.err(declErr);
58338
- let snapshot = cloneConfig(config), issuesBefore = schemaIssueKeys(snapshot), applyErr = this.applyChange(config, resolved, value, action, index, isOverride);
58386
+ let snapshot = cloneConfig(config), issuesBefore = schemaIssueKeys(snapshot), applyErr = this.applyChange(config, resolved, value, action, index, isOverride || isAuthorized);
58339
58387
  if (applyErr) return typeof applyErr == "string" ? this.err(applyErr) : this.errProtected(applyErr, this.lockDescription(path, value, action));
58340
58388
  let issuesAfter = schemaIssueKeys(config), introduced = [...issuesAfter.keys()].filter((key) => !issuesBefore.has(key));
58341
58389
  if (introduced.length > 0 || issuesAfter.size > issuesBefore.size) {
@@ -58421,7 +58469,16 @@ var InputSchema6 = external_exports.object({
58421
58469
  // ---------------------------------------------------------------------------
58422
58470
  // Field validation
58423
58471
  // ---------------------------------------------------------------------------
58424
- validateField(path, value, action) {
58472
+ validateField(path, value, action, workspace) {
58473
+ if (action !== "remove" && /^commands(?:\.\d+(?:\.lambda)?)?$/.test(path)) {
58474
+ let entries = path.endsWith(".lambda") ? [{ lambda: value }] : Array.isArray(value) ? value : [value];
58475
+ for (let entry of entries) {
58476
+ if (!entry || typeof entry != "object" || typeof entry.lambda != "string") continue;
58477
+ let ref = parseLambdaRef(entry.lambda);
58478
+ if ("error" in ref) return ref.error;
58479
+ if (workspace.readFile(ref.file) === null) return `Lambda file "${ref.file}" not found. Write it first, then register the command.`;
58480
+ }
58481
+ }
58425
58482
  if (path === "name") {
58426
58483
  if (typeof value != "string" || value.trim().length === 0)
58427
58484
  return "name must be a non-empty string";
@@ -58496,14 +58553,16 @@ var InputSchema6 = external_exports.object({
58496
58553
  * the tool's effective declaration (current config, else DEFAULT_TOOLS)
58497
58554
  * has that flag true.
58498
58555
  */
58499
- checkDeclarationIntegrity(config, segments, value, action) {
58556
+ checkDeclarationIntegrity(config, segments, value, action, isAuthorized) {
58500
58557
  if (action === "remove") return null;
58501
58558
  let normalized = segments.filter((seg) => typeof seg != "number").join(".");
58502
58559
  if (!DECLARATION_ARRAYS.has(normalized)) return null;
58503
58560
  let isElementSet = action === "set" && typeof segments[segments.length - 1] == "number", incoming = action === "append" ? [value] : isElementSet ? [value] : Array.isArray(value) ? value : [];
58504
58561
  if (incoming.length === 0) return null;
58505
- let existingArray = this.readArrayAt(config, isElementSet ? segments.slice(0, -1) : segments), replacedIndex = isElementSet ? segments[segments.length - 1] : -1, siblings = (existingArray ?? []).filter((_, i) => i !== replacedIndex), seen = /* @__PURE__ */ new Set();
58562
+ let existingArray = this.readArrayAt(config, isElementSet ? segments.slice(0, -1) : segments), replacedIndex = isElementSet ? segments[segments.length - 1] : -1, siblings = normalized === "commands" && action === "set" && !isElementSet ? [] : (existingArray ?? []).filter((_, i) => i !== replacedIndex), seen = /* @__PURE__ */ new Set();
58506
58563
  for (let entry of incoming) {
58564
+ if (normalized === "commands" && !isAuthorized && entry && typeof entry == "object" && entry.locked === !0)
58565
+ return "Only the owner can lock a command.";
58507
58566
  let key = declarationKey(entry);
58508
58567
  if (!key) continue;
58509
58568
  if (seen.has(key) || siblings.some((el) => declarationKey(el) === key))
@@ -59287,6 +59346,7 @@ function mergeTemplateWithOverrides(templateConfig, overrides) {
59287
59346
  "mcp",
59288
59347
  "adapters",
59289
59348
  "serving",
59349
+ "commands",
59290
59350
  "providers",
59291
59351
  "ws_connections",
59292
59352
  "locked_fields",
@@ -59304,7 +59364,10 @@ function mergeTemplateWithOverrides(templateConfig, overrides) {
59304
59364
  if (!mergedTriggers.ok) return mergedTriggers;
59305
59365
  let mergedServing = mergeServing(templateConfig.serving, overrides.serving);
59306
59366
  if (!mergedServing.ok) return mergedServing;
59307
- let options = {
59367
+ let lockedCommands = (templateConfig.commands ?? []).filter((command) => command.locked);
59368
+ if (overrides.commands?.some((command) => lockedCommands.some((locked) => locked.name === command.name)))
59369
+ return { ok: !1, error: "Cannot override a locked template command" };
59370
+ let commands = overrides.commands === void 0 ? templateConfig.commands : [...lockedCommands, ...overrides.commands], options = {
59308
59371
  name: overrides.name ?? templateConfig.name,
59309
59372
  description: overrides.description ?? templateConfig.description,
59310
59373
  instructions: overrides.instructions ?? templateConfig.instructions,
@@ -59333,6 +59396,7 @@ function mergeTemplateWithOverrides(templateConfig, overrides) {
59333
59396
  ...overrides.logging ?? templateConfig.logging ? { logging: overrides.logging ?? templateConfig.logging } : {},
59334
59397
  ...overrides.mcp ?? templateConfig.mcp ? { mcp: overrides.mcp ?? templateConfig.mcp } : {},
59335
59398
  ...overrides.adapters ?? templateConfig.adapters ? { adapters: overrides.adapters ?? templateConfig.adapters } : {},
59399
+ ...commands ? { commands } : {},
59336
59400
  ...mergedServing.value ? { serving: mergedServing.value } : {},
59337
59401
  ...overrides.providers ?? templateConfig.providers ? { providers: overrides.providers ?? templateConfig.providers } : {},
59338
59402
  ...overrides.ws_connections ?? templateConfig.ws_connections ? { ws_connections: overrides.ws_connections ?? templateConfig.ws_connections } : {},
@@ -63701,6 +63765,10 @@ import { join as join8, dirname as dirname5 } from "path";
63701
63765
 
63702
63766
  // src/main/utils/atomic-json.ts
63703
63767
  import {
63768
+ closeSync,
63769
+ fsyncSync,
63770
+ openSync,
63771
+ writeSync,
63704
63772
  readFileSync as readFileSync2,
63705
63773
  writeFileSync as writeFileSync2,
63706
63774
  renameSync as renameSync2,
@@ -64510,9 +64578,13 @@ init_execution_context();
64510
64578
  init_emit_umbilical();
64511
64579
 
64512
64580
  // src/main/runtime/runtime-gate.ts
64513
- var RuntimeGateImpl = class {
64581
+ var KILL_IN_PROGRESS_ERROR = "Kill is still stopping agents. Start it again once it finishes.", RuntimeGateImpl = class {
64514
64582
  _stopped = !1;
64515
64583
  _teardown = !1;
64584
+ _kills = 0;
64585
+ _killEpoch = 0;
64586
+ _killSettled = Promise.resolve();
64587
+ _settleKill = null;
64516
64588
  get stopped() {
64517
64589
  return this._stopped;
64518
64590
  }
@@ -64520,9 +64592,43 @@ var RuntimeGateImpl = class {
64520
64592
  get tearingDown() {
64521
64593
  return this._teardown;
64522
64594
  }
64595
+ /** True while a Kill is stopping agents (between beginKill and its release). */
64596
+ get killing() {
64597
+ return this._kills > 0;
64598
+ }
64599
+ /**
64600
+ * Counts the Kills begun. A start that awaits before it starts anything (a
64601
+ * provider check, a folder scan) takes it first and refuses when
64602
+ * `killedSince` says a Kill began in between: a start the user asked for
64603
+ * before the Kill is not carried out after it.
64604
+ */
64605
+ get killEpoch() {
64606
+ return this._killEpoch;
64607
+ }
64608
+ killedSince(epoch) {
64609
+ return this._killEpoch !== epoch || this._kills > 0;
64610
+ }
64523
64611
  stop() {
64524
64612
  this._stopped || (this._stopped = !0, console.log("[RuntimeGate] stopped"));
64525
64613
  }
64614
+ /**
64615
+ * A Kill begins: the gate closes and stays closed until the returned
64616
+ * release runs (call it in a finally). Overlapping Kills nest. After the
64617
+ * release the gate is still stopped: only a deliberate start reopens it.
64618
+ */
64619
+ beginKill() {
64620
+ this.stop(), this._killEpoch++, this._kills++ === 0 && (this._killSettled = new Promise((resolve20) => {
64621
+ this._settleKill = resolve20;
64622
+ }));
64623
+ let released = !1;
64624
+ return () => {
64625
+ released || (released = !0, --this._kills === 0 && (this._settleKill?.(), this._settleKill = null));
64626
+ };
64627
+ }
64628
+ /** Resolves once no Kill is running (at once when none is). */
64629
+ whenKillSettled() {
64630
+ return this._killSettled;
64631
+ }
64526
64632
  /**
64527
64633
  * Terminal stop for process shutdown. After this, `resume()` no-ops and
64528
64634
  * `stopped` remains true until process exit.
@@ -64535,11 +64641,15 @@ var RuntimeGateImpl = class {
64535
64641
  console.warn("[RuntimeGate] resume() ignored \u2014 runtime teardown in progress");
64536
64642
  return;
64537
64643
  }
64644
+ if (this._kills > 0) {
64645
+ console.warn("[RuntimeGate] resume() refused \u2014 Kill is still stopping agents");
64646
+ return;
64647
+ }
64538
64648
  this._stopped && (this._stopped = !1, console.log("[RuntimeGate] resumed"));
64539
64649
  }
64540
- /** Test-only escape hatch: clears stopped/teardown state. Never call in production code. */
64650
+ /** Test-only escape hatch: clears stopped/teardown/kill state. Never call in production code. */
64541
64651
  _resetForTests() {
64542
- this._stopped = !1, this._teardown = !1;
64652
+ this._stopped = !1, this._teardown = !1, this._kills = 0, this._killEpoch = 0, this._settleKill?.(), this._settleKill = null, this._killSettled = Promise.resolve();
64543
64653
  }
64544
64654
  }, RuntimeGate = new RuntimeGateImpl();
64545
64655
 
@@ -65130,6 +65240,8 @@ var AgentExecutor = class _AgentExecutor extends EventEmitter3 {
65130
65240
  session;
65131
65241
  config;
65132
65242
  basePrompt;
65243
+ /** Settings' app system prompt, read each turn: an edit applies at the next turn (setBasePromptSource). */
65244
+ basePromptSource = null;
65133
65245
  toolPrompts;
65134
65246
  compactionPrompt;
65135
65247
  abortController = null;
@@ -65175,6 +65287,8 @@ var AgentExecutor = class _AgentExecutor extends EventEmitter3 {
65175
65287
  // Turns running OR already committed to run (see isTurnActive). Not a state
65176
65288
  // machine — a counter, because error-recovery retries nest executeTurn calls
65177
65289
  // and a re-entrant successor claims its slot before its predecessor releases.
65290
+ pendingOwnerCommandRecords = [];
65291
+ ownerCommandRecordsClosed = !1;
65178
65292
  activeTurnCount = 0;
65179
65293
  // Agent-scope subset of activeTurnCount. The concurrent-turn guard reads THIS
65180
65294
  // one: a system-scope dispatch (lambda handler) never touches the session, so
@@ -66058,7 +66172,7 @@ var AgentExecutor = class _AgentExecutor extends EventEmitter3 {
66058
66172
  let loop = this.umbilicalLoop(), run2 = () => this.executeTurnImpl(dispatch, opts, turnId), correlated = () => withTurn(dispatch.turnId ?? turnId, () => loop ? withLoop(loop, run2) : run2());
66059
66173
  return await withSource(`agent:${turnId}`, this.config.id, correlated);
66060
66174
  } finally {
66061
- if (this.activeTurnCount--, dispatch.scope !== "system" ? this.activeAgentTurnCount-- : this.activeAgentTurnCount === 0 && this.betweenTurns() && this.drainPendingTriggers(), this.activeAgentTurnCount === 0 && this.writeDiscardNotice(), this.activeTurnCount === 0 && this.onTurnSettled)
66175
+ if (this.activeTurnCount--, dispatch.scope !== "system" ? this.activeAgentTurnCount-- : this.activeAgentTurnCount === 0 && this.betweenTurns() && this.drainPendingTriggers(), this.activeTurnCount === 0 && this.flushOwnerCommandRecords(), this.activeAgentTurnCount === 0 && this.writeDiscardNotice(), this.activeTurnCount === 0 && this.onTurnSettled)
66062
66176
  try {
66063
66177
  this.onTurnSettled();
66064
66178
  } catch (error) {
@@ -66145,7 +66259,7 @@ var AgentExecutor = class _AgentExecutor extends EventEmitter3 {
66145
66259
  } finally {
66146
66260
  this.compactionFailedThisTurn = failedBefore, this.manualCompactionActive = !1, this._interruptRestart = !1;
66147
66261
  let replayed = this.getState() !== "stopped" && this.replayQueuedChats();
66148
- if (this.activeTurnCount--, this.activeAgentTurnCount--, !replayed && this.activeAgentTurnCount === 0 && this.betweenTurns() && this.drainPendingTriggers(), this.activeAgentTurnCount === 0 && this.writeDiscardNotice(), this.activeTurnCount === 0 && this.onTurnSettled)
66262
+ if (this.activeTurnCount--, this.activeAgentTurnCount--, !replayed && this.activeAgentTurnCount === 0 && this.betweenTurns() && this.drainPendingTriggers(), this.activeTurnCount === 0 && this.flushOwnerCommandRecords(), this.activeAgentTurnCount === 0 && this.writeDiscardNotice(), this.activeTurnCount === 0 && this.onTurnSettled)
66149
66263
  try {
66150
66264
  this.onTurnSettled();
66151
66265
  } catch (error) {
@@ -66357,7 +66471,7 @@ var AgentExecutor = class _AgentExecutor extends EventEmitter3 {
66357
66471
  timestamp: Date.now()
66358
66472
  }), chatTokens = await this.forceCompact(`auto: ${chatTokens} >= ${compactThreshold}`)) : (this.memoryFlushNudgeIssued = !0, this.addRuntimeNotice("Context is about to be compacted. Before that happens: write any durable learnings from this loop to your mind pages (update mind.md index + mind/log.md), citing source messages by their [S<seq>] markers. Track unfinished work on an open-thread page. Then continue \u2014 compaction follows automatically.")));
66359
66473
  let systemPrompt = this.buildSystemPrompt(), dynamicInstructions = this.buildDynamicInstructions(chatTokens, compactThreshold);
66360
- if (this.recordRequestContext(systemPrompt, dynamicInstructions), this.session.drainContextInjections(), opts?.nested || this.deliverQueuedChats("next_step"), !this.providerValidated && this.provider) {
66474
+ if (this.recordRequestContext(systemPrompt, dynamicInstructions), this.session.drainContextInjections(), this.flushOwnerCommandRecords(), opts?.nested || this.deliverQueuedChats("next_step"), !this.providerValidated && this.provider) {
66361
66475
  let validation = await this.provider.validateConfig();
66362
66476
  if (!validation.valid) {
66363
66477
  let preflightError = validation.error || "unknown";
@@ -67855,6 +67969,29 @@ Use start_line/end_line to read sections (e.g. start_line=1, end_line=100).
67855
67969
  * Truncate oversized tool results to protect the context window.
67856
67970
  * Uses a fast char-based pre-filter; only tokenizes when borderline.
67857
67971
  */
67972
+ /** Apply the configured tool-result bounds before retaining a command result. */
67973
+ boundToolResult(content, label) {
67974
+ return this.enforceToolResultLimit({ content, isError: !1 }, label).content;
67975
+ }
67976
+ recordOwnerCommand(text) {
67977
+ if (this.ownerCommandRecordsClosed) {
67978
+ try {
67979
+ this.session.getWorkspace().insertLog("warn", "command", "record_discarded", null, "Command completed after executor disposal.");
67980
+ } catch {
67981
+ }
67982
+ return { deferred: !1 };
67983
+ }
67984
+ if (this.isTurnActive())
67985
+ return this.pendingOwnerCommandRecords.push(text), { deferred: !0 };
67986
+ let content = [{ type: "text", text }], seq = this.session.getWorkspace().appendToLoop("user", content);
67987
+ return this.session.getMessages().length > 0 && this.session.addMessage({ role: "user", content }, void 0, { skipLoop: !0, seq }), this.emitEvent({ type: "context_injected", payload: { category: "owner_command", content: text, seq }, timestamp: Date.now() }), { deferred: !1, seq };
67988
+ }
67989
+ flushOwnerCommandRecords() {
67990
+ for (; this.pendingOwnerCommandRecords.length > 0; ) {
67991
+ let text = this.pendingOwnerCommandRecords[0], message = { role: "user", content: [{ type: "text", text }] }, seq;
67992
+ this.session.getMessages().length === 0 ? seq = this.session.getWorkspace().appendToLoop("user", message.content) : (this.session.addMessage(message), this.session.flushToLoop(), seq = message.seq), this.pendingOwnerCommandRecords.shift(), this.emitEvent({ type: "context_injected", payload: { category: "owner_command", content: text, seq }, timestamp: Date.now() });
67993
+ }
67994
+ }
67858
67995
  enforceToolResultLimit(result, toolName) {
67859
67996
  let maxTokens = this.config.limits?.max_tool_result_tokens ?? 16e3, content = result.content;
67860
67997
  if (content.length <= maxTokens * 3) return result;
@@ -67891,7 +68028,7 @@ ${tail}` : ""}`;
67891
68028
  [TRUNCATED \u2014 content exceeded ${maxTokens.toLocaleString()} token limit]`;
67892
68029
  }
67893
68030
  setState(state) {
67894
- state === "idle" && this.besideTurnHilTasks.size > 0 && (state = "awaiting_approval"), state !== "error" && (this._errorReason = null, this._recoverySuppressedLogged = !1), this.state = state, this.emitEvent({
68031
+ state === "idle" && this.besideTurnHilTasks.size > 0 && (state = "awaiting_approval"), state !== "error" && (this._errorReason = null, this._recoverySuppressedLogged = !1), state !== "stopped" && (this.ownerCommandRecordsClosed = !1), this.state = state, this.emitEvent({
67895
68032
  type: "state_changed",
67896
68033
  payload: { state },
67897
68034
  timestamp: Date.now()
@@ -68417,7 +68554,21 @@ ${tail}` : ""}`;
68417
68554
  prompt
68418
68555
  }, prompt;
68419
68556
  }
68557
+ /**
68558
+ * Read the app system prompt from the host's settings at every turn, so an
68559
+ * edit applies at the next turn, not the next start. A non-string value
68560
+ * keeps the prompt the executor has.
68561
+ */
68562
+ setBasePromptSource(read) {
68563
+ this.basePromptSource = read;
68564
+ }
68565
+ /** The app system prompt this executor assembles with now. */
68566
+ getBasePrompt() {
68567
+ return this.basePrompt;
68568
+ }
68420
68569
  assembleSystemPrompt() {
68570
+ let live = this.basePromptSource?.();
68571
+ typeof live == "string" && (this.basePrompt = live);
68421
68572
  let enabledToolNames = this.config.tools.filter((t) => t.enabled).map((t) => t.name).sort(), enabledTools = new Set(enabledToolNames), bare = this.config.bare_prompt === !0, includeBase = !bare && this.config.include_base_prompt !== !1, fullSections = assemblePrompt({
68422
68573
  config: this.config,
68423
68574
  basePrompt: this.basePrompt,
@@ -68432,6 +68583,8 @@ ${this.config.instructions}`
68432
68583
  JSON.stringify({
68433
68584
  name: this.config.name,
68434
68585
  instructions: this.config.instructions,
68586
+ // The app system prompt can change mid-session (setBasePromptSource).
68587
+ base_prompt: this.hashString(this.basePrompt),
68435
68588
  include_base_prompt: this.config.include_base_prompt,
68436
68589
  bare_prompt: this.config.bare_prompt,
68437
68590
  tools: enabledToolNames,
@@ -68661,6 +68814,7 @@ You have native ${modalityList} perception enabled. Two ways to perceive media:
68661
68814
  * a System notice goes to the live transcript, adf_logs and the loop.
68662
68815
  */
68663
68816
  discardQueuedChats(reason) {
68817
+ this.activeAgentTurnCount === 0 && this.flushOwnerCommandRecords(), this.ownerCommandRecordsClosed = !0;
68664
68818
  let chats = this.pendingChats.splice(0), unanswered = this.absorbedTurnIds.splice(0);
68665
68819
  if (this.runningTurnId && !unanswered.includes(this.runningTurnId) && unanswered.push(this.runningTurnId), chats.length === 0 && unanswered.length === 0) return;
68666
68820
  let ids = chats.flatMap((chat) => chat.turnId ? [chat.turnId] : []);
@@ -70273,12 +70427,12 @@ var LoopConfigInputSchema = external_exports.object({
70273
70427
  name: external_exports.string().regex(LOOP_NAME_PATTERN, { message: LOOP_NAME_RULE }).optional().describe(
70274
70428
  'Loop name: 1-32 lowercase chars; unique here, "main" reserved.'
70275
70429
  ),
70276
- goal: external_exports.string().min(1).max(LOOP_GOAL_MAX_CHARS).optional().describe(
70277
- "The loop's charter \u2014 it becomes the loop's instructions."
70430
+ goal: external_exports.string().max(LOOP_GOAL_MAX_CHARS).optional().describe(
70431
+ "The loop's charter, appended to its instructions. Optional: without one the loop works on whatever it is sent. Required when autostart is true."
70278
70432
  ),
70279
70433
  enabled: external_exports.boolean().optional().describe("Whether the loop runs. Default true on create."),
70280
70434
  autostart: external_exports.boolean().optional().describe(
70281
- "Run a first turn on the goal without waiting to be addressed: now on create, and again whenever the agent starts. Default true on create. false: it only runs when a trigger, timer or loop_send targets it."
70435
+ "Run a first turn on the goal without waiting to be addressed: now on create, and again whenever the agent starts. Default true on create, and needs a goal: creating without a goal requires autostart false. false: it only runs when a trigger, timer or loop_send targets it."
70282
70436
  ),
70283
70437
  autonomous: external_exports.boolean().optional().describe(
70284
70438
  "Keep turning after text-only responses until the loop calls sys_set_state. Not inherited from you; default false. Grant sys_set_state alongside."
@@ -70360,7 +70514,7 @@ var LoopManageTool = class {
70360
70514
  // Mutations
70361
70515
  // ---------------------------------------------------------------------------
70362
70516
  async handleCreate(pool, workspace, nameArg, configArg) {
70363
- if (!configArg) return errorResult('create requires "config" (at least name and goal).');
70517
+ if (!configArg) return errorResult('create requires "config" (at least a name).');
70364
70518
  let name15 = configArg.name ?? nameArg;
70365
70519
  if (!name15) return errorResult('create requires a loop name, in "config.name" or "name".');
70366
70520
  if (configArg.name && nameArg && configArg.name !== nameArg)
@@ -70376,7 +70530,7 @@ var LoopManageTool = class {
70376
70530
  );
70377
70531
  let candidate = {
70378
70532
  name: name15,
70379
- goal: configArg.goal,
70533
+ ...configArg.goal !== void 0 && { goal: configArg.goal },
70380
70534
  enabled: configArg.enabled ?? !0,
70381
70535
  // Persisted, like the agent's own `autostart`: the pool re-kicks the loop
70382
70536
  // on every agent start. Default on — a loop nobody addresses is idle,
@@ -70409,7 +70563,7 @@ var LoopManageTool = class {
70409
70563
  }
70410
70564
  return {
70411
70565
  content: `Created loop "${name15}"${validated.config.enabled ? " (running)" : " (disabled)"}.
70412
- Goal: ${validated.config.goal}
70566
+ Goal: ${validated.config.goal?.trim() ? validated.config.goal : "(none)"}
70413
70567
  Tools: ${toolLine}.${this.disabledNote(validated.disabled)}
70414
70568
  ` + startLine,
70415
70569
  isError: !1
@@ -75277,7 +75431,7 @@ var LoopPool = class _LoopPool {
75277
75431
  isMain: !0
75278
75432
  }, side = (host2.loops ?? []).map((loop) => ({
75279
75433
  name: loop.name,
75280
- goal: loop.goal,
75434
+ goal: loop.goal ?? "",
75281
75435
  status: this.runtimes.get(loop.name)?.status() ?? "idle",
75282
75436
  enabled: loop.enabled !== !1,
75283
75437
  isMain: !1
@@ -75475,13 +75629,19 @@ var LoopPool = class _LoopPool {
75475
75629
  if (this.disposed) return [];
75476
75630
  let woken = [];
75477
75631
  for (let loop of this.declaredLoops())
75478
- if (!(!loop.autostart || loop.enabled === !1) && this.runtimes.has(loop.name))
75632
+ if (!(!loop.autostart || loop.enabled === !1) && this.runtimes.has(loop.name)) {
75633
+ if (!hasLoopGoal(loop)) {
75634
+ let message = `Autostart of loop "${loop.name}" refused: it has no goal. Add a goal or turn autostart off.`;
75635
+ console.warn(`[LoopPool] ${message}`), this.logLoop("warn", "loop_autostart_refused", loop.name, message);
75636
+ continue;
75637
+ }
75479
75638
  try {
75480
75639
  let result = await this.sendToLoop(MAIN_LOOP, loop.name, LOOP_AUTOSTART_MESSAGE, !0);
75481
75640
  result.woke ? woken.push(loop.name) : console.warn(`[LoopPool] Autostart of loop "${loop.name}" delivered but did not wake: ${result.reason ?? "unknown"}`);
75482
75641
  } catch (error) {
75483
75642
  console.error(`[LoopPool] Autostart of loop "${loop.name}" failed:`, error);
75484
75643
  }
75644
+ }
75485
75645
  return woken;
75486
75646
  }
75487
75647
  // ===========================================================================
@@ -75623,14 +75783,7 @@ var LoopPool = class _LoopPool {
75623
75783
  dropLoopTimers(name15) {
75624
75784
  let dropped = 0, kept = 0;
75625
75785
  try {
75626
- for (let timer of this.deps.workspace.getTimers())
75627
- if ((timer.loop ?? MAIN_LOOP) === name15) {
75628
- if (timer.locked) {
75629
- kept++;
75630
- continue;
75631
- }
75632
- this.deps.workspace.deleteTimer(timer.id), dropped++;
75633
- }
75786
+ ({ dropped, kept } = dropTimersForLoop(this.deps.workspace, name15));
75634
75787
  } catch (error) {
75635
75788
  console.error(`[LoopPool] Failed to drop timers for loop "${name15}":`, error);
75636
75789
  }
@@ -75741,7 +75894,7 @@ var LoopPool = class _LoopPool {
75741
75894
  this.deps.compactionPrompt,
75742
75895
  preLlmHookRunner
75743
75896
  );
75744
- callHandler?.attachSession(session);
75897
+ this.deps.basePromptSource && executor.setBasePromptSource(this.deps.basePromptSource), callHandler?.attachSession(session);
75745
75898
  let modelHandler = callHandler ?? this.deps.adfCallHandler;
75746
75899
  executor.setModelProviderFactory(modelHandler ? (model) => modelHandler.providerForModel(model) : null);
75747
75900
  let runtime2 = new LoopRuntime({
@@ -76114,14 +76267,14 @@ function assembleAgent(options) {
76114
76267
  options.compactionPrompt,
76115
76268
  preLlmHookRunner
76116
76269
  );
76117
- executor.setModelProviderFactory(adfCallHandler ? (model) => adfCallHandler.providerForModel(model) : null), executor.recoverStaleTurnCheckpoint(), executor.reconcileOrphanedTasks(), validateConfigOnLoad(workspace, config);
76270
+ options.basePromptSource && executor.setBasePromptSource(options.basePromptSource), executor.setModelProviderFactory(adfCallHandler ? (model) => adfCallHandler.providerForModel(model) : null), executor.recoverStaleTurnCheckpoint(), executor.reconcileOrphanedTasks(), validateConfigOnLoad(workspace, config);
76118
76271
  let snapshotConfig = config, refreshEffectiveRuntime = () => {
76119
76272
  let sources = options.effectiveRuntime;
76120
76273
  sources && recordEffectiveRuntime(workspace, () => buildEffectiveRuntime({
76121
76274
  host: profile,
76122
76275
  config: snapshotConfig,
76123
76276
  provider: executor.getProvider() ?? provider,
76124
- basePrompt: options.basePrompt ?? "",
76277
+ basePrompt: executor.getBasePrompt(),
76125
76278
  toolPrompts: options.toolPrompts ?? {},
76126
76279
  compactionPrompt: options.compactionPrompt,
76127
76280
  sources,
@@ -76337,6 +76490,7 @@ ${json.slice(0, room)}${note}`;
76337
76490
  // model override must follow it.
76338
76491
  getProvider: () => executor.getProvider() ?? provider,
76339
76492
  basePrompt: options.basePrompt ?? "",
76493
+ basePromptSource: options.basePromptSource,
76340
76494
  toolPrompts: options.toolPrompts ?? {},
76341
76495
  compactionPrompt: options.compactionPrompt,
76342
76496
  adfCallHandler,
@@ -78424,18 +78578,69 @@ function createDefaultPipeline() {
78424
78578
  }
78425
78579
 
78426
78580
  // src/main/services/attestation.service.ts
78427
- var REPLACEABLE_ROLES = ["owner", "operator"], RESERVED_ROLES = /* @__PURE__ */ new Set(["owner", "operator", "runtime", "clone", "rotation"]);
78581
+ var REPLACEABLE_ROLES = ["owner", "operator"], RESERVED_ROLES = /* @__PURE__ */ new Set(["owner", "operator", "runtime", "clone", "rotation"]), ATTESTATION_CLAIMS_MAX_BYTES = 4096, ATTESTATION_MAX_BYTES = 8192, PEER_ATTESTATIONS_MAX_COUNT = 64, PEER_ATTESTATIONS_MAX_BYTES = 65536, ATTESTATION_CLAIMS_MAX_DEPTH = 8;
78582
+ function attestationClaimsProblem(claims) {
78583
+ if (claims === null || typeof claims != "object" || Array.isArray(claims)) return "claims must be a JSON object";
78584
+ let problem = jsonValueProblem(claims, 1);
78585
+ if (problem) return problem;
78586
+ let bytes = Buffer.byteLength(canonicalJsonStringify(claims));
78587
+ return bytes > ATTESTATION_CLAIMS_MAX_BYTES ? `claims take ${bytes} bytes; the most is ${ATTESTATION_CLAIMS_MAX_BYTES}` : null;
78588
+ }
78589
+ function jsonValueProblem(value, depth) {
78590
+ if (value === null || typeof value == "string" || typeof value == "boolean") return null;
78591
+ if (typeof value == "number") return Number.isFinite(value) ? null : "claims numbers must be finite";
78592
+ if (typeof value != "object") return `claims cannot hold a ${typeof value}`;
78593
+ if (depth > ATTESTATION_CLAIMS_MAX_DEPTH) return `claims nest deeper than ${ATTESTATION_CLAIMS_MAX_DEPTH} levels`;
78594
+ if (Array.isArray(value)) {
78595
+ for (let item of value) {
78596
+ let problem = jsonValueProblem(item, depth + 1);
78597
+ if (problem) return problem;
78598
+ }
78599
+ return null;
78600
+ }
78601
+ let proto = Object.getPrototypeOf(value);
78602
+ if (proto !== Object.prototype && proto !== null) return "claims can hold only plain objects";
78603
+ if (Object.prototype.hasOwnProperty.call(value, "__proto__")) return 'claims cannot hold a "__proto__" key';
78604
+ for (let item of Object.values(value)) {
78605
+ let problem = jsonValueProblem(item, depth + 1);
78606
+ if (problem) return problem;
78607
+ }
78608
+ return null;
78609
+ }
78610
+ function fieldsProblem(fields) {
78611
+ for (let key of ["issuer", "subject", "role", "issued_at"])
78612
+ if (typeof fields[key] != "string") return `${key} must be a string`;
78613
+ for (let key of ["expires_at", "scope"])
78614
+ if (fields[key] !== void 0 && typeof fields[key] != "string") return `${key} must be a string`;
78615
+ return Object.prototype.hasOwnProperty.call(fields, "__proto__") ? 'an attestation cannot hold a "__proto__" key' : null;
78616
+ }
78428
78617
  function signableBytes(fields) {
78618
+ if (fields.v === 2) return Buffer.from(canonicalJsonStringify(fields));
78429
78619
  let { issuer, subject, role, issued_at, expires_at, scope } = fields, signable = { issuer, subject, role, issued_at };
78430
78620
  return expires_at !== void 0 && (signable.expires_at = expires_at), scope !== void 0 && (signable.scope = scope), Buffer.from(canonicalJsonStringify(signable));
78431
78621
  }
78432
78622
  function createAttestation(fields, privateKeyPkcs8) {
78433
- let signature = `ed25519:${signEd25519(signableBytes(fields), privateKeyPkcs8)}`;
78434
- return { ...fields, signature };
78623
+ let fieldProblem = fieldsProblem(fields);
78624
+ if (fieldProblem) throw new Error(`Attestation not signed: ${fieldProblem}`);
78625
+ if (fields.claims !== void 0) {
78626
+ let problem = attestationClaimsProblem(fields.claims);
78627
+ if (problem) throw new Error(`Attestation not signed: ${problem}`);
78628
+ }
78629
+ let unsigned = { v: 2, ...fields }, signature = `ed25519:${signEd25519(signableBytes(unsigned), privateKeyPkcs8)}`;
78630
+ return { ...unsigned, signature };
78435
78631
  }
78436
78632
  function verifyAttestation(att, opts) {
78437
- if (!att || typeof att.signature != "string" || !att.signature.startsWith("ed25519:") || opts?.expectedSubject && att.subject !== opts.expectedSubject) return !1;
78438
- if (att.expires_at) {
78633
+ if (!att || typeof att != "object" || typeof att.signature != "string" || !att.signature.startsWith("ed25519:") || fieldsProblem(att) || att.v !== void 0 && att.v !== 2) return !1;
78634
+ if (att.v === 2) {
78635
+ try {
78636
+ if (Buffer.byteLength(canonicalJsonStringify(att)) > ATTESTATION_MAX_BYTES) return !1;
78637
+ } catch {
78638
+ return !1;
78639
+ }
78640
+ if (att.claims !== void 0 && attestationClaimsProblem(att.claims)) return !1;
78641
+ }
78642
+ if (opts?.expectedSubject && att.subject !== opts.expectedSubject) return !1;
78643
+ if (att.expires_at !== void 0) {
78439
78644
  let expires = Date.parse(att.expires_at);
78440
78645
  if (Number.isNaN(expires) || expires <= (opts?.now ?? /* @__PURE__ */ new Date()).getTime()) return !1;
78441
78646
  }
@@ -78473,26 +78678,72 @@ function addPeerAttestation(workspace, attestation, ownDid) {
78473
78678
  return { ok: !1, error: `Attestation subject must be this agent's DID (${ownDid})` };
78474
78679
  if (!verifyAttestation(attestation, { expectedSubject: ownDid }))
78475
78680
  return { ok: !1, error: "Attestation signature is invalid or expired" };
78476
- if (readAdfAttestations(workspace).some((a) => a.signature === attestation.signature))
78681
+ let existing = readAdfAttestations(workspace);
78682
+ if (existing.some((a) => a.signature === attestation.signature))
78477
78683
  return { ok: !0 };
78478
- let { issuer, subject, issued_at, expires_at, scope, signature } = attestation;
78479
- return workspace.getDatabase().insertAttestation({ issuer, subject, role, issued_at, expires_at, scope, signature }), { ok: !0 };
78684
+ let peers = existing.filter((a) => !RESERVED_ROLES.has(a.role));
78685
+ if (peers.length >= PEER_ATTESTATIONS_MAX_COUNT)
78686
+ return { ok: !1, error: `This agent already keeps ${peers.length} peer attestations, the most it stores` };
78687
+ let peerBytes = peers.reduce((n, a) => n + Buffer.byteLength(JSON.stringify(a)), 0) + Buffer.byteLength(JSON.stringify(attestation));
78688
+ if (peerBytes > PEER_ATTESTATIONS_MAX_BYTES)
78689
+ return { ok: !1, error: `Storing it would take peer attestations to ${peerBytes} bytes; the most is ${PEER_ATTESTATIONS_MAX_BYTES}` };
78690
+ if (attestation.v === 2)
78691
+ workspace.getDatabase().insertAttestation(attestation);
78692
+ else {
78693
+ let { issuer, subject, issued_at, expires_at, scope, signature } = attestation;
78694
+ workspace.getDatabase().insertAttestation({ issuer, subject, role, issued_at, expires_at, scope, signature });
78695
+ }
78696
+ return { ok: !0 };
78480
78697
  }
78481
78698
  function issuePeerAttestation(workspace, fields, signingKeyPkcs8) {
78482
78699
  let issuer = workspace.getDid();
78483
78700
  if (!issuer) return { ok: !1, error: "This agent has no DID" };
78484
78701
  let role = typeof fields.role == "string" ? fields.role.trim() : "";
78485
- return role ? RESERVED_ROLES.has(role) ? { ok: !1, error: `Role "${role}" is reserved for the runtime` } : typeof fields.subject != "string" || !didToPublicKey(fields.subject) ? { ok: !1, error: "Subject must be a valid did:key identifier" } : fields.subject === issuer ? { ok: !1, error: "An agent cannot attest to itself" } : fields.expires_at !== void 0 && Number.isNaN(Date.parse(fields.expires_at)) ? { ok: !1, error: "expires_at must be an ISO 8601 timestamp" } : { ok: !0, attestation: createAttestation(
78702
+ if (!role) return { ok: !1, error: "Attestation role is required" };
78703
+ if (RESERVED_ROLES.has(role)) return { ok: !1, error: `Role "${role}" is reserved for the runtime` };
78704
+ if (typeof fields.subject != "string" || !didToPublicKey(fields.subject))
78705
+ return { ok: !1, error: "Subject must be a valid did:key identifier" };
78706
+ if (fields.subject === issuer) return { ok: !1, error: "An agent cannot attest to itself" };
78707
+ if (fields.expires_at !== void 0 && Number.isNaN(Date.parse(fields.expires_at)))
78708
+ return { ok: !1, error: "expires_at must be an ISO 8601 timestamp" };
78709
+ if (fields.claims !== void 0) {
78710
+ let problem = attestationClaimsProblem(fields.claims);
78711
+ if (problem) return { ok: !1, error: problem };
78712
+ }
78713
+ return { ok: !0, attestation: createAttestation(
78486
78714
  {
78487
78715
  issuer,
78488
78716
  subject: fields.subject,
78489
78717
  role,
78490
78718
  issued_at: (/* @__PURE__ */ new Date()).toISOString(),
78491
78719
  ...fields.expires_at !== void 0 ? { expires_at: fields.expires_at } : {},
78492
- ...fields.scope !== void 0 ? { scope: String(fields.scope) } : {}
78720
+ ...fields.scope !== void 0 ? { scope: String(fields.scope) } : {},
78721
+ ...fields.claims !== void 0 ? { claims: fields.claims } : {}
78493
78722
  },
78494
78723
  signingKeyPkcs8
78495
- ) } : { ok: !1, error: "Attestation role is required" };
78724
+ ) };
78725
+ }
78726
+ function upgradeOwnAttestations(workspace, signers, busyTimeoutMs) {
78727
+ let keys = new Map(signers.filter((s) => s.did && s.privateKey).map((s) => [s.did, s.privateKey]));
78728
+ if (keys.size === 0) return 0;
78729
+ let updates = [];
78730
+ for (let att of readAdfAttestations(workspace)) {
78731
+ let key = att.v === void 0 ? keys.get(att.issuer) : void 0;
78732
+ if (!key || !verifyAttestation(att, { now: /* @__PURE__ */ new Date(0) })) continue;
78733
+ let { issuer, subject, role, issued_at, expires_at, scope } = att;
78734
+ updates.push({
78735
+ from: att.signature,
78736
+ to: createAttestation({
78737
+ issuer,
78738
+ subject,
78739
+ role,
78740
+ issued_at,
78741
+ ...expires_at !== void 0 ? { expires_at } : {},
78742
+ ...scope !== void 0 ? { scope } : {}
78743
+ }, key)
78744
+ });
78745
+ }
78746
+ return updates.length === 0 ? 0 : workspace.immediateTransaction(() => workspace.getDatabase().resignAttestations(updates), busyTimeoutMs);
78496
78747
  }
78497
78748
  function issueOwnerAttestation(workspace, keys) {
78498
78749
  let subject = workspace.getDid();
@@ -79245,7 +79496,8 @@ var EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["say", "ask"]), CODE_EXECUTION_MET
79245
79496
  subject: input.subject,
79246
79497
  role: input.role,
79247
79498
  scope: input.scope,
79248
- expires_at: input.expires_at
79499
+ expires_at: input.expires_at,
79500
+ ...input.claims !== void 0 ? { claims: input.claims } : {}
79249
79501
  }, signingKey);
79250
79502
  return result.ok ? (this.logCall("info", "attestation_issue", input.role, `Issued "${input.role}" attestation for ${input.subject}`), { result: result.attestation }) : (this.logCall("warn", "attestation_issue", input.role, `Rejected: ${result.error}`), { error: result.error, errorCode: "INVALID_INPUT" });
79251
79503
  } catch (err) {
@@ -79503,14 +79755,16 @@ var EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["say", "ask"]), CODE_EXECUTION_MET
79503
79755
  properties: {
79504
79756
  attestation: {
79505
79757
  type: "object",
79506
- description: "The complete signed attestation object, exactly as returned by the issuer's attestation_issue",
79758
+ description: "The complete signed attestation object, exactly as returned by the issuer's attestation_issue (v2 signs every field, so pass it unchanged)",
79507
79759
  properties: {
79760
+ v: { type: "number", description: "Format version: 2 for certs issued now (every field signed); absent on older v1 certs" },
79508
79761
  issuer: { type: "string", description: "DID of the signer" },
79509
79762
  subject: { type: "string", description: "This agent's DID" },
79510
79763
  role: { type: "string", description: 'What the issuer asserts (e.g. "member", "reviewer") \u2014 reserved roles rejected' },
79511
79764
  issued_at: { type: "string", description: "ISO 8601 timestamp" },
79512
79765
  expires_at: { type: "string", description: "Optional ISO 8601 expiry" },
79513
79766
  scope: { type: "string", description: "Optional qualifier (e.g. group or project identifier)" },
79767
+ claims: { type: "object", description: "Optional JSON the issuer signed with the cert" },
79514
79768
  signature: { type: "string", description: "Issuer's Ed25519 signature over the canonical payload" }
79515
79769
  },
79516
79770
  required: ["issuer", "subject", "role", "issued_at", "signature"]
@@ -79528,7 +79782,8 @@ var EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["say", "ask"]), CODE_EXECUTION_MET
79528
79782
  subject: { type: "string", description: "DID of the agent being attested (did:key:..., not this agent)" },
79529
79783
  role: { type: "string", description: 'What is being asserted (e.g. "member", "reviewer") \u2014 reserved roles rejected' },
79530
79784
  scope: { type: "string", description: "Optional qualifier (e.g. group or project identifier)" },
79531
- expires_at: { type: "string", description: "Optional ISO 8601 expiry" }
79785
+ expires_at: { type: "string", description: "Optional ISO 8601 expiry" },
79786
+ claims: { type: "object", description: 'Optional JSON object signed with the cert (at most 4096 bytes as canonical JSON, 8 levels deep, plain JSON values only). The runtime does not interpret it; namespace the keys, e.g. {"org.example/membership": {"tier": "editor"}}' }
79532
79787
  },
79533
79788
  required: ["subject", "role"]
79534
79789
  }
@@ -79700,7 +79955,7 @@ if (typeof ${fnName} === 'function') {
79700
79955
  let { event } = dispatch, triggerName = Object.entries(TRIGGER_TO_EVENT_TYPE).find(([, v]) => v === event.type)?.[0] ?? event.type;
79701
79956
  console.log(`[Shell] Executing trigger command: "${command}" (trigger: ${event.type})`);
79702
79957
  try {
79703
- let { parse: parse2 } = await import("./parser-7GARNT5L.mjs"), { executeNode: executeNode2 } = await import("./pipeline-executor-FD4FBWQA.mjs"), { EnvironmentResolver: EnvironmentResolver2 } = await import("./environment-R47X44GM.mjs"), config = this.workspace.getAgentConfig(), env = new EnvironmentResolver2(config, this.workspace);
79958
+ let { parse: parse2 } = await import("./parser-7GARNT5L.mjs"), { executeNode: executeNode2 } = await import("./pipeline-executor-DRYEEFDR.mjs"), { EnvironmentResolver: EnvironmentResolver2 } = await import("./environment-R47X44GM.mjs"), config = this.workspace.getAgentConfig(), env = new EnvironmentResolver2(config, this.workspace);
79704
79959
  env.setTriggerContext(dispatch);
79705
79960
  let toolRegistry = this.adfCallHandler.getToolRegistry(), ast = parse2(command), startTime = performance.now(), result = await executeNode2(ast, "", {
79706
79961
  workspace: this.workspace,
@@ -83793,6 +84048,7 @@ var MCP_CONNECT_BUDGET_MS = 25e3, AgentRuntimeBuilder = class {
83793
84048
  ...this.settings ? { effectiveRuntime: { settings: this.settings } } : {},
83794
84049
  restoreLoop: opts.restoreLoop,
83795
84050
  basePrompt: opts.prompts ? opts.prompts.basePrompt : this.basePrompt,
84051
+ basePromptSource: opts.prompts?.basePromptSource,
83796
84052
  toolPrompts: opts.prompts ? opts.prompts.toolPrompts : this.toolPrompts,
83797
84053
  compactionPrompt: opts.prompts ? opts.prompts.compactionPrompt : this.compactionPrompt,
83798
84054
  adfCallHandler,
@@ -84698,6 +84954,73 @@ function applyBackgroundEvent(target, filePath, event, emit, now) {
84698
84954
  Object.assign(target, step.patch), step.event && emit(step.event);
84699
84955
  }
84700
84956
 
84957
+ // src/main/runtime/owner-command.ts
84958
+ init_execution_context();
84959
+ init_emit_umbilical();
84960
+ var OwnerCommandRefusedError = class extends Error {
84961
+ constructor(message, statusCode, code) {
84962
+ super(message);
84963
+ this.statusCode = statusCode;
84964
+ this.code = code;
84965
+ this.name = "OwnerCommandRefusedError";
84966
+ }
84967
+ statusCode;
84968
+ code;
84969
+ };
84970
+ async function runOwnerCommand(host2, req) {
84971
+ if (!req || typeof req.name != "string" || typeof req.args != "string" || req.args.length > 4096 || /[\r\n]/.test(req.args) || typeof req.authorize != "boolean" || req.loop !== void 0 && typeof req.loop != "string" || req.requestId !== void 0 && (typeof req.requestId != "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(req.requestId)))
84972
+ throw new OwnerCommandRefusedError("Invalid command request: args must be a single line of at most 4096 characters and requestId must use 1\u201364 letters, digits, underscores or hyphens.", 400, "invalid_request");
84973
+ let id = req.requestId ?? "cmd_" + nanoid(10), loop = req.loop || MAIN_LOOP;
84974
+ if (!host2.loopPool.hasLoop(loop)) throw new OwnerCommandRefusedError(`No loop named "${loop}" on this agent.`, 404, "loop_not_found");
84975
+ let runtime2 = loop === MAIN_LOOP ? null : host2.loopPool.getRuntime(loop), executor = loop === MAIN_LOOP ? host2.executor : runtime2?.executor, handler = loop === MAIN_LOOP ? host2.adfCallHandler : runtime2?.callHandler, workspace = loop === MAIN_LOOP ? host2.workspace : runtime2?.workspace;
84976
+ if (!executor || !workspace) throw new OwnerCommandRefusedError(`Loop "${loop}" is not running (it is disabled or stopping). Enable it, then run the command again.`, 409, "loop_not_running");
84977
+ if (executor.getState() === "stopped") throw new OwnerCommandRefusedError(`Agent is off. Start it, then run /${req.name} again.`, 409, "agent_not_running");
84978
+ let sandbox = host2.codeSandboxService;
84979
+ if (!sandbox || !handler) throw new OwnerCommandRefusedError("This runtime has no code sandbox, so agent commands cannot run here.", 409, "code_execution_unavailable");
84980
+ let config = host2.workspace.getAgentConfig(), command = Array.isArray(config.commands) ? config.commands.find((c) => c && typeof c == "object" && c.name === req.name) : void 0;
84981
+ if (!command) throw new OwnerCommandRefusedError(`No command "/${req.name}" on this agent. Add it under Commands in the agent's config.`, 404, "command_not_found");
84982
+ let lambda = typeof command.lambda == "string" ? command.lambda : JSON.stringify(command.lambda) ?? "undefined", started = performance.now(), status = "error", authorized = !1, body;
84983
+ try {
84984
+ let ref = parseLambdaRef(lambda);
84985
+ if ("error" in ref) throw new Error(ref.error);
84986
+ let { file, fn } = ref, source = await loadLambdaSource((p) => workspace.readFile(p), file);
84987
+ if (source === null) throw new Error(`Lambda file "${file}" not found. Fix commands[].lambda for /${req.name} or create the file.`);
84988
+ authorized = req.authorize === !0 || workspace.isFileAuthorized(file);
84989
+ let args2 = splitArgv(req.args).join(" "), invocation = { command: req.name, args: args2, argv: splitArgv(args2), invoker: "owner", loop, authorized, id }, missing = `Function "${fn}" not found in "${file}". Declare it at the top level, or fix commands[].lambda for /${req.name}.`, wrapped = `${transformExports(transformImports(source))}
84990
+ if (typeof ${fn} === 'function') { return await ${fn}(${JSON.stringify(invocation)}); }
84991
+ throw new Error(${JSON.stringify(missing)});`, sandboxId = `${config.id}:cmd:${file}:${id}:${nanoid(10)}`, payload = { lambda_path: file, function_name: fn, kind: "owner_command", command: req.name, loop, authorized };
84992
+ emitUmbilicalEvent({ event_type: "lambda.started", agentId: config.id, source: `lambda:${file}:${fn}`, payload });
84993
+ try {
84994
+ let run2 = () => sandbox.execute(
84995
+ sandboxId,
84996
+ wrapped,
84997
+ config.limits.execution_timeout_ms,
84998
+ (method, callArgs) => withAuthorization(authorized, () => handler.handleCall(method, callArgs)),
84999
+ { enabledTools: handler.getEnabledToolNames(), hilTools: handler.getHilToolNames(), isAuthorized: authorized },
85000
+ { handlerAuthorized: authorized, agent: config.id, ephemeral: !0 }
85001
+ ), result = await withSource(`lambda:${file}:${fn}`, config.id, () => withLoop(loop, run2));
85002
+ status = result.error ? ["REQUIRES_AUTHORIZED_CODE", "REQUIRES_AUTHORIZED_CALLER", "PROTECTION_DENIED"].includes(result.errorCode ?? "") ? "denied" : "error" : "ok", body = result.error ?? result.result ?? "(no return value)", result.stdout && (body += `
85003
+
85004
+ Output:
85005
+ ${result.stdout}`), emitUmbilicalEvent({ event_type: result.error ? "lambda.failed" : "lambda.completed", agentId: config.id, source: `lambda:${file}:${fn}`, payload: { ...payload, duration_ms: performance.now() - started, ...result.error ? { error: result.error } : {} } });
85006
+ } catch (error) {
85007
+ throw emitUmbilicalEvent({ event_type: "lambda.failed", agentId: config.id, source: `lambda:${file}:${fn}`, payload: { ...payload, duration_ms: performance.now() - started, error: error instanceof Error ? error.message : String(error) } }), error;
85008
+ } finally {
85009
+ sandbox.destroy(sandboxId);
85010
+ }
85011
+ } catch (error) {
85012
+ body = error instanceof Error ? error.message : String(error);
85013
+ }
85014
+ let durationMs = +(performance.now() - started).toFixed(2);
85015
+ body = executor.boundToolResult(body, "/" + req.name);
85016
+ let args = splitArgv(req.args).join(" "), record = executor.recordOwnerCommand(formatOwnerCommandRow({ v: 1, id, command: "/" + req.name + (args ? " " + args : ""), lambda, authorized, status, duration_ms: durationMs }, body));
85017
+ try {
85018
+ workspace.insertLog(status === "ok" ? "info" : "error", "command", "run", "/" + req.name, `${status} (${durationMs}ms)`, { status, duration_ms: durationMs, authorized, loop });
85019
+ } catch {
85020
+ }
85021
+ return { id, name: req.name, loop, status, authorized, result: body, durationMs, ...record };
85022
+ }
85023
+
84701
85024
  // src/main/runtime/interrupt-turn.ts
84702
85025
  var InterruptRefusedError = class extends Error {
84703
85026
  constructor(message, statusCode) {
@@ -84802,7 +85125,7 @@ function mcpServersWithSealedCredentials(workspace) {
84802
85125
  return [];
84803
85126
  }
84804
85127
  }
84805
- var DUPLICATE_AGENT_ID_CODE = "duplicate_agent_id", AGENT_NOT_RUNNING_CODE = "agent_not_running", DOCUMENT_HOLDER_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, DOCUMENT_HOLD_TTL_MS = 6e4, RuntimeLoopError = class extends Error {
85128
+ var DUPLICATE_AGENT_ID_CODE = "duplicate_agent_id", AGENT_NOT_RUNNING_CODE = "agent_not_running", DOCUMENT_HOLDER_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, DOCUMENT_HOLD_TTL_MS = 6e4, KILL_STOPPED_AUTOSTART = "A Kill stopped this autostart. Start the agent again.", RuntimeLoopError = class extends Error {
84806
85129
  /** `details`: extra fields for the HTTP error body (e.g. `candidates`). */
84807
85130
  constructor(message, statusCode, code, details) {
84808
85131
  super(message);
@@ -84873,6 +85196,7 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
84873
85196
  super(), this.settings = opts.settings, this.providerFactory = opts.providerFactory, this.basePrompt = opts.basePrompt ?? "", this.toolPrompts = opts.toolPrompts ?? {}, this.compactionPrompt = opts.compactionPrompt, this.agentRuntimeBuilder = opts.agentRuntimeBuilder, this.enforceReviewGate = opts.enforceReviewGate ?? !0, this.agentLeases = opts.agentLeases === void 0 ? processAgentLeases() : opts.agentLeases, this.documentHoldTtlMs = opts.documentHoldTtlMs ?? DOCUMENT_HOLD_TTL_MS, this.agentLeases?.addHostedPathsProbe(() => [...this.filePathToAgentId.keys(), ...this.inFlightLoads.keys()]);
84874
85197
  }
84875
85198
  async loadAgent(filePath, opts = {}) {
85199
+ if (RuntimeGate.killing) throw new RuntimeLoopError(KILL_IN_PROGRESS_ERROR, 409, "kill_in_progress");
84876
85200
  let canonicalPath = this.canonicalFilePath(filePath), existingId = this.filePathToAgentId.get(canonicalPath);
84877
85201
  if (existingId) return this.toRef(this.requireAgent(existingId));
84878
85202
  let pending = this.inFlightLoads.get(canonicalPath);
@@ -84885,6 +85209,24 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
84885
85209
  this.inFlightLoads.delete(canonicalPath), await this.agentLeases?.reconcile();
84886
85210
  }
84887
85211
  }
85212
+ /**
85213
+ * Waits for the loads in flight to finish (or fail), so a Kill can unload
85214
+ * what they registered. Answers the files still loading after `timeoutMs`:
85215
+ * a stuck load never holds up the rest of the Kill.
85216
+ */
85217
+ async settleLoads(timeoutMs = 1e4) {
85218
+ let timer, timedOut = new Promise((resolve20) => {
85219
+ timer = setTimeout(resolve20, timeoutMs);
85220
+ }), settled = (async () => {
85221
+ for (; this.inFlightLoads.size > 0; ) await Promise.allSettled([...this.inFlightLoads.values()]);
85222
+ })();
85223
+ try {
85224
+ await Promise.race([settled, timedOut]);
85225
+ } finally {
85226
+ clearTimeout(timer);
85227
+ }
85228
+ return [...this.inFlightLoads.keys()];
85229
+ }
84888
85230
  /**
84889
85231
  * Resolves true once a load of `filePath` in flight has finished (or
84890
85232
  * failed), at once when none is; false when it is still loading after
@@ -85350,6 +85692,7 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
85350
85692
  * active-state greeting, as Studio's foreground start does.
85351
85693
  */
85352
85694
  async startAgent(agentId, opts = {}) {
85695
+ if (RuntimeGate.killing) throw new RuntimeLoopError(KILL_IN_PROGRESS_ERROR, 409, "kill_in_progress");
85353
85696
  RuntimeGate.resume();
85354
85697
  let managed = this.requireAgent(agentId), lifecycle = managed.agent.getLifecycleState();
85355
85698
  if (lifecycle !== "running")
@@ -85368,15 +85711,21 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
85368
85711
  if (raced.status === "rejected") throw raced.err;
85369
85712
  return raced.status === "resolved" ? raced.dispatched : !0;
85370
85713
  }
85714
+ /**
85715
+ * `killEpoch`: RuntimeGate.killEpoch when the caller's start began (before
85716
+ * its own awaits); a Kill since then refuses the start.
85717
+ */
85371
85718
  async startOrLoadAgent(identifier, opts = {}) {
85372
- let managed = this.resolveAgent(identifier), loaded = !1;
85719
+ let killEpoch = opts.killEpoch ?? RuntimeGate.killEpoch, managed = this.resolveAgent(identifier), loaded = !1;
85373
85720
  if (!managed) {
85374
85721
  let filePath = this.documents.resolve(identifier)?.filePath ?? this.findAgentFile(identifier);
85375
85722
  if (!filePath) throw new Error(`RuntimeService: unknown agent "${identifier}"`);
85376
85723
  let ref = await this.loadAgent(filePath);
85377
85724
  managed = this.requireAgent(ref.id), loaded = !0;
85378
85725
  }
85379
- let startupTriggered = await this.startAgent(managed.id, opts);
85726
+ if (RuntimeGate.killedSince(killEpoch))
85727
+ throw loaded && await this.unloadAgent(managed.id, { mode: "immediate" }), new RuntimeLoopError(KILL_IN_PROGRESS_ERROR, 409, "kill_in_progress");
85728
+ let startupTriggered = await this.startAgent(managed.id, { hasUserMessage: opts.hasUserMessage });
85380
85729
  return {
85381
85730
  ref: this.toRef(managed),
85382
85731
  loaded,
@@ -85423,7 +85772,16 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
85423
85772
  throw err instanceof InterruptRefusedError ? new RuntimeLoopError(err.message, err.statusCode) : err;
85424
85773
  }
85425
85774
  }
85775
+ async runAgentCommand(agentId, name15, body) {
85776
+ let managed = this.requireAgent(agentId);
85777
+ try {
85778
+ return await runOwnerCommand(managed.agent, { ...body, name: name15, args: body.args ?? "", authorize: body.authorize === !0 });
85779
+ } catch (err) {
85780
+ throw err instanceof OwnerCommandRefusedError ? new RuntimeLoopError(err.message, err.statusCode, err.code) : err;
85781
+ }
85782
+ }
85426
85783
  async autostartFromDirectories(trackedDirs2, opts = {}) {
85784
+ let killEpoch = RuntimeGate.killEpoch;
85427
85785
  RuntimeGate.resume();
85428
85786
  let files = await this.scanAdfFilesAsync(trackedDirs2, opts.maxDepth ?? 5), report = {
85429
85787
  scanned: files.length,
@@ -85454,7 +85812,11 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
85454
85812
  if (!isConfigReviewed(this.settings?.get("reviewedAgents"), bootResult.config))
85455
85813
  return { kind: "skipped", entry: { filePath, name: name15, reason: "unreviewed", agentId: boot.agentId } };
85456
85814
  try {
85457
- let ref = await this.loadAgent(filePath, { enforceReviewGate: !1 }), startupTriggered = await this.startAgent(ref.id);
85815
+ if (RuntimeGate.killedSince(killEpoch)) throw new RuntimeLoopError(KILL_STOPPED_AUTOSTART, 409, "kill_in_progress");
85816
+ let ref = await this.loadAgent(filePath, { enforceReviewGate: !1 });
85817
+ if (RuntimeGate.killedSince(killEpoch))
85818
+ throw await this.unloadAgent(ref.id, { mode: "immediate" }), new RuntimeLoopError(KILL_STOPPED_AUTOSTART, 409, "kill_in_progress");
85819
+ let startupTriggered = await this.startAgent(ref.id);
85458
85820
  return { kind: "started", entry: { agentId: ref.id, filePath, name: ref.config.name, startupTriggered } };
85459
85821
  } catch (err) {
85460
85822
  return err instanceof RuntimeReviewRequiredError ? { kind: "skipped", entry: { filePath, name: name15, reason: "unreviewed", agentId: err.agentId } } : err instanceof RuntimeLoopError && err.code === AGENT_LEASED_CODE ? (console.warn(`[RuntimeService] Autostart skipped: ${err.message}`), { kind: "skipped", entry: { filePath, name: name15, reason: "leased_elsewhere", agentId: boot.agentId, message: err.message } }) : {
@@ -85994,7 +86356,7 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
85994
86356
  effectiveTools: null
85995
86357
  }, side = (config.loops ?? []).map((loop) => ({
85996
86358
  name: loop.name,
85997
- goal: loop.goal,
86359
+ goal: loop.goal ?? "",
85998
86360
  status: "idle",
85999
86361
  enabled: loop.enabled !== !1,
86000
86362
  isMain: !1,
@@ -86011,7 +86373,7 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
86011
86373
  if (pool.hasLoop(input.name)) throw new RuntimeLoopError(`A loop named "${input.name}" already exists.`, 409);
86012
86374
  let host2 = managed.agent.workspace.getAgentConfig(), available = new Set(listAvailableLoopTools(host2)), candidate = {
86013
86375
  name: input.name,
86014
- goal: input.goal,
86376
+ ...input.goal !== void 0 ? { goal: input.goal } : {},
86015
86377
  enabled: input.enabled ?? !0,
86016
86378
  autostart: input.autostart ?? !0,
86017
86379
  ...input.autonomous !== void 0 ? { autonomous: input.autonomous } : {},
@@ -86128,7 +86490,9 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
86128
86490
  let target = this.requireTarget(agentId);
86129
86491
  if (this.assertBaseRevision("config", configRevision(target.workspace), opts.baseRevision, null), !target.managed) {
86130
86492
  let doc = target.doc;
86131
- stripLoopNameMarker(config), doc.workspace.setAgentConfig(config), doc.config = config, doc.configWrites++;
86493
+ stripLoopNameMarker(config);
86494
+ let previousConfig2 = doc.workspace.getAgentConfig();
86495
+ doc.workspace.setAgentConfig(config), await teardownRemovedLoopsOffline(doc.workspace, previousConfig2, config), doc.config = config, doc.configWrites++;
86132
86496
  let revision2 = configRevision(doc.workspace, currentResourceWriter()).revision, rename2 = this.syncAgentFileToName(doc.filePath, config.name);
86133
86497
  return {
86134
86498
  agentId: target.id,
@@ -87127,6 +87491,7 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
87127
87491
  registerAgent(agent, filePath, config, idOverride, keyCell = { current: null }) {
87128
87492
  if (RuntimeGate.tearingDown)
87129
87493
  throw new Error("RuntimeService: runtime teardown in progress \u2014 agent not registered.");
87494
+ if (RuntimeGate.killing) throw new RuntimeLoopError(KILL_IN_PROGRESS_ERROR, 409, "kill_in_progress");
87130
87495
  let id = idOverride ?? config.id;
87131
87496
  this.assertNoDuplicateId(id, filePath);
87132
87497
  let managed = {
@@ -87505,6 +87870,7 @@ var LOOP_PATCH_FIELDS = ["goal", "enabled", "autostart", "autonomous", "model",
87505
87870
  let base = this.settings?.get("globalSystemPrompt"), tools = this.settings?.get("toolPrompts"), compaction = this.settings?.get("compactionPrompt");
87506
87871
  return {
87507
87872
  basePrompt: typeof base == "string" ? base : this.basePrompt,
87873
+ ...this.settings ? { basePromptSource: () => this.settings?.get("globalSystemPrompt") } : {},
87508
87874
  toolPrompts: tools && typeof tools == "object" && !Array.isArray(tools) ? tools : this.toolPrompts,
87509
87875
  compactionPrompt: typeof compaction == "string" ? compaction : this.compactionPrompt
87510
87876
  };
@@ -109134,6 +109500,70 @@ data: {"cursor":13,"kind":"executor","agentId":"\u2026","filePath":"/agents/agen
109134
109500
  }
109135
109501
  ]
109136
109502
  }
109503
+ },
109504
+ "/agents/{id}/commands/{name}/run": {
109505
+ post: {
109506
+ tags: [
109507
+ "Loops"
109508
+ ],
109509
+ summary: "Run an owner slash command",
109510
+ description: "Runs a registered lambda without a model turn and records its bounded outcome in the selected loop. Requires a running agent; never starts it. Error and denied outcomes are recorded and return 200. The authorize boolean elevates only this invocation; chat text is never interpreted as a command.",
109511
+ operationId: "runAgentCommand",
109512
+ parameters: [
109513
+ {
109514
+ $ref: "#/components/parameters/AgentIdPath"
109515
+ },
109516
+ {
109517
+ name: "name",
109518
+ in: "path",
109519
+ required: !0,
109520
+ description: "Registered command name without the slash.",
109521
+ schema: {
109522
+ type: "string"
109523
+ }
109524
+ }
109525
+ ],
109526
+ requestBody: {
109527
+ required: !1,
109528
+ content: {
109529
+ "application/json": {
109530
+ schema: {
109531
+ $ref: "#/components/schemas/OwnerCommandBody"
109532
+ }
109533
+ }
109534
+ }
109535
+ },
109536
+ responses: {
109537
+ "400": {
109538
+ $ref: "#/components/responses/BadRequest"
109539
+ },
109540
+ "401": {
109541
+ $ref: "#/components/responses/Unauthorized"
109542
+ },
109543
+ "403": {
109544
+ $ref: "#/components/responses/Forbidden"
109545
+ },
109546
+ "404": {
109547
+ $ref: "#/components/responses/NotFound"
109548
+ },
109549
+ "409": {
109550
+ $ref: "#/components/responses/Conflict"
109551
+ },
109552
+ "500": {
109553
+ $ref: "#/components/responses/ServerError"
109554
+ },
109555
+ "200": {
109556
+ description: "Recorded command outcome",
109557
+ content: {
109558
+ "application/json": {
109559
+ schema: {
109560
+ $ref: "#/components/schemas/OwnerCommandResult"
109561
+ }
109562
+ }
109563
+ }
109564
+ }
109565
+ }
109566
+ }
109137
109567
  }
109138
109568
  },
109139
109569
  components: {
@@ -110949,8 +111379,27 @@ data: {"cursor":13,"kind":"executor","agentId":"\u2026","filePath":"/agents/agen
110949
111379
  additionalProperties: {
110950
111380
  type: "object",
110951
111381
  properties: {
110952
- support: { type: "object", properties: { image: { type: "boolean" }, audio: { type: "boolean" }, video: { type: "boolean" } } },
110953
- refusals: { type: "object", additionalProperties: { type: "string" }, description: "Why each modality not in support is refused, keyed image, audio or video." }
111382
+ support: {
111383
+ type: "object",
111384
+ properties: {
111385
+ image: {
111386
+ type: "boolean"
111387
+ },
111388
+ audio: {
111389
+ type: "boolean"
111390
+ },
111391
+ video: {
111392
+ type: "boolean"
111393
+ }
111394
+ }
111395
+ },
111396
+ refusals: {
111397
+ type: "object",
111398
+ additionalProperties: {
111399
+ type: "string"
111400
+ },
111401
+ description: "Why each modality not in support is refused, keyed image, audio or video."
111402
+ }
110954
111403
  }
110955
111404
  },
110956
111405
  description: "GET /agents/{id}/status of a running agent: each loop's effective media (main and live inner loops, keyed by loop name): what its provider connector carries, narrowed by the agent's model.multimodal toggles. POST /agents/{id}/chat refuses the rest with 400 media_unsupported."
@@ -123623,6 +124072,79 @@ data: {"cursor":13,"kind":"executor","agentId":"\u2026","filePath":"/agents/agen
123623
124072
  }
123624
124073
  }
123625
124074
  ]
124075
+ },
124076
+ OwnerCommandBody: {
124077
+ type: "object",
124078
+ properties: {
124079
+ args: {
124080
+ type: "string",
124081
+ maxLength: 4096,
124082
+ pattern: "^[^\\n]*$",
124083
+ default: ""
124084
+ },
124085
+ loop: {
124086
+ type: "string",
124087
+ default: "main"
124088
+ },
124089
+ authorize: {
124090
+ type: "boolean",
124091
+ default: !1,
124092
+ description: "Owner grant for this invocation only. Paired devices may grant it."
124093
+ },
124094
+ requestId: {
124095
+ type: "string",
124096
+ pattern: "^[A-Za-z0-9_-]{1,64}$"
124097
+ }
124098
+ }
124099
+ },
124100
+ OwnerCommandResult: {
124101
+ type: "object",
124102
+ required: [
124103
+ "id",
124104
+ "name",
124105
+ "loop",
124106
+ "status",
124107
+ "authorized",
124108
+ "result",
124109
+ "durationMs",
124110
+ "deferred"
124111
+ ],
124112
+ properties: {
124113
+ id: {
124114
+ type: "string"
124115
+ },
124116
+ name: {
124117
+ type: "string"
124118
+ },
124119
+ loop: {
124120
+ type: "string"
124121
+ },
124122
+ status: {
124123
+ type: "string",
124124
+ enum: [
124125
+ "ok",
124126
+ "error",
124127
+ "denied"
124128
+ ]
124129
+ },
124130
+ authorized: {
124131
+ type: "boolean"
124132
+ },
124133
+ result: {
124134
+ type: "string",
124135
+ description: "Bounded output exactly as recorded in the loop."
124136
+ },
124137
+ durationMs: {
124138
+ type: "number"
124139
+ },
124140
+ deferred: {
124141
+ type: "boolean",
124142
+ description: "The record waits for the current step to finish."
124143
+ },
124144
+ seq: {
124145
+ type: "integer"
124146
+ }
124147
+ }
123626
124148
  }
123627
124149
  }
123628
124150
  }
@@ -126198,6 +126720,18 @@ function rotateDaemonEncKey(settingsDir) {
126198
126720
  return { key: ensureDaemonEncKey(settingsDir), retiredLabel: current.label };
126199
126721
  }
126200
126722
 
126723
+ // src/main/services/secret-storage-policy.ts
126724
+ var SECRET_STORAGE_POLICY_KEY = "secretStoragePolicy";
126725
+ var SETTINGS_SECRET_KEYS = ["ownerMnemonic", "runtimePrivateKey", "runtimeEncPrivateKey", "mcpOauthCredentials"], SUPERSEDED_MNEMONIC_PREFIX = "ownerMnemonicSuperseded:";
126726
+ function isSecretSettingsKey(key) {
126727
+ return SETTINGS_SECRET_KEYS.includes(key) || key.startsWith(SUPERSEDED_MNEMONIC_PREFIX);
126728
+ }
126729
+ var SecretStorageRefusedError = class extends Error {
126730
+ constructor(message) {
126731
+ super(message), this.name = "SecretStorageRefusedError";
126732
+ }
126733
+ };
126734
+
126201
126735
  // src/main/services/owner-identity.service.ts
126202
126736
  var OWNER_SEED_DERIVED_KEY = "ownerDidSeedDerived";
126203
126737
  function ownerMnemonicSupersededKey(ownerDid) {
@@ -126294,6 +126828,11 @@ var OwnerIdentityService = class {
126294
126828
  }
126295
126829
  return this.settings.setSecret("ownerMnemonic", phrase), currentOwnerDid || (this.settings.set("ownerDid", owner.did), this.settings.set("ownerSeedBackupConfirmed", !1)), this.markSeedDerived(), console.log(opts.replacingStale ? `[OwnerIdentity] The owner is now ${owner.did} (switched outside this app, e.g. adf identity import): replaced this install's phrase of the previous owner with the one in the OS keychain` : `[OwnerIdentity] Imported owner phrase for ${owner.did} from the OS keychain`), phrase;
126296
126830
  }
126831
+ /** Throws when secrets cannot be saved right now (Studio's secret-storage choice), before any write. */
126832
+ assertSecretsWritable() {
126833
+ let refusal = this.settings.secretWriteRefusal?.() ?? null;
126834
+ if (refusal) throw new SecretStorageRefusedError(refusal);
126835
+ }
126297
126836
  // =========================================================================
126298
126837
  // Identity bootstrap + migration
126299
126838
  // =========================================================================
@@ -126343,6 +126882,7 @@ var OwnerIdentityService = class {
126343
126882
  runtimeDid: this.settings.get("runtimeDid") ?? "",
126344
126883
  migrated: !1
126345
126884
  };
126885
+ this.assertSecretsWritable();
126346
126886
  let isUpgrade = !!existingOwnerDid;
126347
126887
  if (isUpgrade) {
126348
126888
  let legacyOwner = this.settings.get("legacyOwnerDids") ?? [];
@@ -126419,11 +126959,23 @@ var OwnerIdentityService = class {
126419
126959
  let runtimeDid = this.getRuntimeDid();
126420
126960
  if (!runtimeDid || keyStatus === "absent") return this.mintRuntimeKey(ownerKey, ownerDid);
126421
126961
  let delegation = this.getRuntimeDelegation();
126422
- return (!delegation || delegation.issuer !== ownerDid || !verifyAttestation(delegation, { expectedSubject: runtimeDid })) && this.settings.set("runtimeDelegation", createAttestation(
126962
+ return (!delegation || delegation.v !== 2 || delegation.issuer !== ownerDid || !verifyAttestation(delegation, { expectedSubject: runtimeDid })) && this.settings.set("runtimeDelegation", createAttestation(
126423
126963
  { issuer: ownerDid, subject: runtimeDid, role: "runtime", issued_at: (/* @__PURE__ */ new Date()).toISOString() },
126424
126964
  ownerKey
126425
126965
  )), runtimeDid;
126426
126966
  }
126967
+ /** Own v1 attestations in the file re-signed as v2 (attestation.service.ts). Logged, never thrown. */
126968
+ upgradeAttestations(workspace) {
126969
+ try {
126970
+ let upgraded = upgradeOwnAttestations(workspace, [
126971
+ { did: this.getOwnerDid(), privateKey: this.getOwnerSigningKey() },
126972
+ { did: this.getRuntimeDid(), privateKey: this.getRuntimeSigningKey() }
126973
+ ], PROVISION_BUSY_TIMEOUT_MS);
126974
+ upgraded > 0 && console.log(`[OwnerIdentity] Re-signed ${upgraded} attestation(s) as v2 in ${workspace.getFilePath()}`);
126975
+ } catch (err) {
126976
+ console.warn("[OwnerIdentity] Attestation v2 upgrade failed:", err);
126977
+ }
126978
+ }
126427
126979
  /** Record that `ownerDid` is seed-derived (idempotent; no write when already set). */
126428
126980
  markSeedDerived() {
126429
126981
  this.settings.get(OWNER_SEED_DERIVED_KEY) !== !0 && this.settings.set(OWNER_SEED_DERIVED_KEY, !0);
@@ -126532,7 +127084,7 @@ var OwnerIdentityService = class {
126532
127084
  let currentOwnerDid = this.getOwnerDid(), owner = deriveOwnerIdentity(normalized);
126533
127085
  if (opts.expectedOwnerDid && owner.did !== opts.expectedOwnerDid)
126534
127086
  throw new Error(`That phrase belongs to ${owner.did}, not this machine's owner ${opts.expectedOwnerDid}. Check the words, or use Import identity to switch owners.`);
126535
- if (currentOwnerDid && currentOwnerDid !== owner.did) {
127087
+ if (this.assertSecretsWritable(), currentOwnerDid && currentOwnerDid !== owner.did) {
126536
127088
  let legacy = this.settings.get("legacyOwnerDids") ?? [];
126537
127089
  legacy.includes(currentOwnerDid) || this.settings.set("legacyOwnerDids", [...legacy, currentOwnerDid]);
126538
127090
  }
@@ -126694,7 +127246,7 @@ var OwnerIdentityService = class {
126694
127246
  }, signingRow = workspace.getIdentityRow("crypto:signing:private_key");
126695
127247
  signingRow === null ? (workspace.generateIdentityKeys(null, opts.adoptKeys), keysGenerated = !0, stampAndAttest()) : signingRow.encryption_algo === "plain" && workspace.getDid() && !workspace.getMeta("adf_owner_did") && this.getOwnerSigningKey() && stampAndAttest();
126696
127248
  let sealed = workspace.sealPlainRowsIntoEnvelopes();
126697
- return { keysGenerated, sealed };
127249
+ return this.upgradeAttestations(workspace), { keysGenerated, sealed };
126698
127250
  };
126699
127251
  if (!(!workspace.hasEnvelopes() && this.getEnvelopeRecipients() !== null || workspace.getIdentityRow("crypto:signing:private_key") === null)) return provision();
126700
127252
  try {
@@ -126986,7 +127538,7 @@ function normalizeDelegation(raw) {
126986
127538
  if (!isNonEmptyString(d[field])) return null;
126987
127539
  for (let field of ["expires_at", "scope"])
126988
127540
  if (d[field] !== void 0 && typeof d[field] != "string") return null;
126989
- return {
127541
+ return d.v === 2 ? d : {
126990
127542
  issuer: d.issuer,
126991
127543
  subject: d.subject,
126992
127544
  role: d.role,
@@ -127813,22 +128365,45 @@ var TEST_KEYCHAIN_FILE_ENV = "ADF_TEST_KEYCHAIN_FILE", TestKeychainFileBackend =
127813
128365
  function blockingSleep(ms) {
127814
128366
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
127815
128367
  }
128368
+ var SecretStoreSelectionError = class extends Error {
128369
+ constructor(message) {
128370
+ super(message), this.name = "SecretStoreSelectionError";
128371
+ }
128372
+ }, STRICT_KEYCHAIN_REMEDY = `The daemon does not start, and it does not open the passphrase file ${SECRET_FILE_NAME}. Make the OS keychain usable and start the daemon again, or set ADF_SECRET_STORE=file (or unset it) to use the passphrase file.`;
127816
128373
  function selectBackend(settingsPath2, deps = {}) {
127817
- let forced = process.env.ADF_SECRET_STORE, file = () => new FileSecretBackend(join33(dirname17(settingsPath2), SECRET_FILE_NAME));
128374
+ let forced = process.env.ADF_SECRET_STORE || void 0;
128375
+ if (forced !== void 0 && forced !== "file" && forced !== "keychain")
128376
+ throw new SecretStoreSelectionError('ADF_SECRET_STORE has an unsupported value. Set it to "keychain" or "file", or unset it to let the daemon choose.');
128377
+ let strict = forced === "keychain", file = () => new FileSecretBackend(join33(dirname17(settingsPath2), SECRET_FILE_NAME));
127818
128378
  if (forced === "file") return { backend: file(), keychainError: null };
127819
- if (process.env.ADF_KEYCHAIN === "0" || process.env.ADF_KEYCHAIN === "off") return { backend: file(), keychainError: null };
128379
+ let keychainOff = process.env.ADF_KEYCHAIN;
128380
+ if (keychainOff === "0" || keychainOff === "off") {
128381
+ if (strict)
128382
+ throw new SecretStoreSelectionError(
128383
+ `ADF_SECRET_STORE=keychain conflicts with ADF_KEYCHAIN=${keychainOff}, which turns the OS keychain off. Unset ADF_KEYCHAIN to use the keychain, or set ADF_SECRET_STORE=file (or unset it) to use the passphrase file.`
128384
+ );
128385
+ return { backend: file(), keychainError: null };
128386
+ }
127820
128387
  let fakePath = process.env[TEST_KEYCHAIN_FILE_ENV];
127821
- if (fakePath)
127822
- return testSeamsAllowed() ? (console.warn(`[ADF Daemon] Test seam: ${fakePath} stands in for the OS keychain (ADF_TEST_SEAMS=1)`), { backend: new TestKeychainFileBackend(fakePath), keychainError: null }) : (console.error(`[ADF Daemon] Ignoring ${TEST_KEYCHAIN_FILE_ENV}: test seams need ADF_TEST_SEAMS=1 and an unpackaged build. Using the passphrase file, not the OS keychain.`), { backend: file(), keychainError: null });
127823
- let probe = deps.probe ?? probeKeychain, sleep = deps.sleep ?? blockingSleep, platform3 = deps.platform ?? process.platform, result = probe(), expected = result.present && (platform3 === "win32" || platform3 === "darwin");
128388
+ if (fakePath) {
128389
+ if (testSeamsAllowed())
128390
+ return console.warn(`[ADF Daemon] Test seam: ${fakePath} stands in for the OS keychain (ADF_TEST_SEAMS=1)`), { backend: new TestKeychainFileBackend(fakePath), keychainError: null };
128391
+ if (strict) throw new SecretStoreSelectionError(`ADF_SECRET_STORE=keychain, but ${TEST_KEYCHAIN_FILE_ENV} is set without the test seams (ADF_TEST_SEAMS=1 and an unpackaged build). ${STRICT_KEYCHAIN_REMEDY}`);
128392
+ return console.error(`[ADF Daemon] Ignoring ${TEST_KEYCHAIN_FILE_ENV}: test seams need ADF_TEST_SEAMS=1 and an unpackaged build. Using the passphrase file, not the OS keychain.`), { backend: file(), keychainError: null };
128393
+ }
128394
+ let probe = deps.probe ?? probeKeychain, sleep = deps.sleep ?? blockingSleep, platform3 = deps.platform ?? process.platform, result = probe(), tries = 1, expected = result.present && (platform3 === "win32" || platform3 === "darwin");
127824
128395
  for (let i = 0; !result.backend && expected && i < KEYCHAIN_PROBE_DELAYS_MS.length; i++)
127825
- console.warn(`[ADF Daemon] The OS keychain did not answer (${result.error ?? "unknown error"}); trying again in ${KEYCHAIN_PROBE_DELAYS_MS[i] / 1e3}s`), sleep(KEYCHAIN_PROBE_DELAYS_MS[i]), result = probe();
128396
+ console.warn(`[ADF Daemon] The OS keychain did not answer (${result.error ?? "unknown error"}); trying again in ${KEYCHAIN_PROBE_DELAYS_MS[i] / 1e3}s`), sleep(KEYCHAIN_PROBE_DELAYS_MS[i]), result = probe(), tries++;
127826
128397
  if (result.backend) return { backend: result.backend, keychainError: null };
128398
+ if (strict) {
128399
+ let why = result.present ? `the OS keychain could not be read${tries > 1 ? ` after ${tries} tries` : ""} (${result.error ?? "unknown error"})` : "no OS keychain binding (@napi-rs/keyring) loads in this build";
128400
+ throw new SecretStoreSelectionError(`ADF_SECRET_STORE=keychain, but ${why}. ${STRICT_KEYCHAIN_REMEDY}`);
128401
+ }
127827
128402
  if (expected) {
127828
128403
  let error = result.error ?? "unknown error";
127829
128404
  return console.error(`[ADF Daemon] The OS keychain could not be read after ${KEYCHAIN_PROBE_DELAYS_MS.length + 1} tries (${error}): using the passphrase file ${SECRET_FILE_NAME}. Owner create, restore and trust-daemon are refused until the daemon restarts with the keychain (adf daemon restart).`), { backend: file(), keychainError: error };
127830
128405
  }
127831
- return forced === "keychain" && console.error("[ADF Daemon] ADF_SECRET_STORE=keychain but no OS keychain is usable \u2014 using the passphrase file instead"), { backend: file(), keychainError: null };
128406
+ return { backend: file(), keychainError: null };
127832
128407
  }
127833
128408
  function readBootPassphrase(readFile3) {
127834
128409
  if (process.env.ADF_OWNER_PASSPHRASE) return process.env.ADF_OWNER_PASSPHRASE;
@@ -128007,7 +128582,7 @@ var ProviderChecks = class {
128007
128582
  cache = /* @__PURE__ */ new Map();
128008
128583
  /** A check the host starts on its own: honours the opt-out (see file header). */
128009
128584
  async autoCheck(cfg) {
128010
- return this.opts.isEnabled() ? this.test(cfg) : this.cache.get(providerTestCacheKey(cfg)) ?? "unchecked";
128585
+ return !this.opts.isEnabled() && !isSubscriptionType(cfg.type) ? this.cache.get(providerTestCacheKey(cfg)) ?? "unchecked" : this.test(cfg);
128011
128586
  }
128012
128587
  async test(cfg, force = !1) {
128013
128588
  let cacheKey = providerTestCacheKey(cfg);
@@ -128017,15 +128592,15 @@ var ProviderChecks = class {
128017
128592
  let finish = (result) => (this.cache.set(cacheKey, result), result);
128018
128593
  if (cfg.type === "chatgpt-subscription") {
128019
128594
  let { getChatGptAuthManager: getChatGptAuthManager2 } = await import("./auth-manager-AJ32FZWS.mjs");
128020
- return finish(getChatGptAuthManager2().isAuthenticated() ? "ok" : "unconfigured");
128595
+ return getChatGptAuthManager2().isAuthenticated() ? "ok" : "unconfigured";
128021
128596
  }
128022
128597
  if (cfg.type === "grok-subscription") {
128023
128598
  let { getGrokAuthManager: getGrokAuthManager2 } = await import("./auth-manager-C5OBEQK2.mjs");
128024
- return finish(getGrokAuthManager2().isAuthenticated() ? "ok" : "unconfigured");
128599
+ return getGrokAuthManager2().isAuthenticated() ? "ok" : "unconfigured";
128025
128600
  }
128026
128601
  if (cfg.type === "claude-subscription") {
128027
128602
  let { getClaudeAuthManager: getClaudeAuthManager2 } = await import("./auth-manager-TBNKCJ45.mjs");
128028
- return finish(await getClaudeAuthManager2().isAuthenticated() ? "ok" : "unconfigured");
128603
+ return await getClaudeAuthManager2().isAuthenticated() ? "ok" : "unconfigured";
128029
128604
  }
128030
128605
  if (cfg.type === "anthropic") {
128031
128606
  if (!cfg.apiKey) return finish("unconfigured");
@@ -128067,6 +128642,14 @@ var ProviderChecks = class {
128067
128642
  import { constants as fsConstants3, createReadStream, createWriteStream as createWriteStream2, existsSync as existsSync19 } from "node:fs";
128068
128643
  import { copyFile, link, lstat as lstat3, mkdir as mkdir2, mkdtemp as mkdtemp2, open as open2, readdir as readdir7, readFile as readFile2, realpath as realpath4, rename, rmdir, stat as stat5, unlink, writeFile as writeFile2 } from "node:fs/promises";
128069
128644
  import { execFile as execFile11 } from "node:child_process";
128645
+
128646
+ // src/main/utils/agents-folder-name.ts
128647
+ function defaultAgentsFolderName(env = process.env) {
128648
+ let instance3 = env.ADF_INSTANCE?.trim().replace(/[^\w.-]/g, "_");
128649
+ return instance3 ? `adf-agents-${instance3}` : "adf-agents";
128650
+ }
128651
+
128652
+ // src/main/daemon/file-ops.ts
128070
128653
  import { homedir as homedir5, tmpdir as tmpdir8 } from "node:os";
128071
128654
  import { basename as basename14, delimiter as delimiter2, dirname as dirname19, isAbsolute as isAbsolute6, join as join36, resolve as resolve15 } from "node:path";
128072
128655
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -129612,10 +130195,10 @@ async function findDocumentsDir() {
129612
130195
  return fallback;
129613
130196
  }
129614
130197
  async function resolveAgentsFolder(settings2) {
129615
- return configuredAgentsFolder(settings2) ?? { path: join36(await documentsDir(), "adf-agents"), source: "default" };
130198
+ return configuredAgentsFolder(settings2) ?? { path: join36(await documentsDir(), defaultAgentsFolderName()), source: "default" };
129616
130199
  }
129617
130200
  function resolveAgentsFolderSync(settings2) {
129618
- return configuredAgentsFolder(settings2) ?? { path: join36(documentsDirSync(), "adf-agents"), source: "default" };
130201
+ return configuredAgentsFolder(settings2) ?? { path: join36(documentsDirSync(), defaultAgentsFolderName()), source: "default" };
129619
130202
  }
129620
130203
  function configuredAgentsFolder(settings2) {
129621
130204
  let configured = settings2?.get("agentsFolder");
@@ -133478,6 +134061,14 @@ function registerFleetRoutes(server, ctx) {
133478
134061
  let success = await manager.restart(type);
133479
134062
  return { success, error: success ? void 0 : `Could not restart the ${type} channel.` };
133480
134063
  }), server.get("/network/peers/health", async (request) => probePeerHealth(request.query.url)), server.get("/network/peers/shared-file", async (request) => fetchPeerSharedFile(request.query.baseUrl, request.query.path)), server.post("/runtime/emergency-stop", async () => {
134064
+ let releaseKill = RuntimeGate.beginKill();
134065
+ try {
134066
+ return await emergencyStop();
134067
+ } finally {
134068
+ releaseKill();
134069
+ }
134070
+ });
134071
+ let emergencyStop = async () => {
133481
134072
  console.log("[EmergencyStop] Shutting down everything...");
133482
134073
  let deps = gapDeps(ctx), skipped = [], failed = [], step = async (name15, run2) => {
133483
134074
  try {
@@ -133486,12 +134077,21 @@ function registerFleetRoutes(server, ctx) {
133486
134077
  failed.push(name15), console.error(`[EmergencyStop] ${name15} error:`, e);
133487
134078
  }
133488
134079
  };
133489
- RuntimeGate.stop(), await step("mesh", () => {
134080
+ await step("mesh", () => {
133490
134081
  mesh?.isEnabled() && (mesh.disableMesh ? mesh.disableMesh() : skipped.push("mesh"));
133491
134082
  });
133492
- let agents = runtime2.listAgents();
133493
- await Promise.all(agents.map((agent) => step(`agent ${agent.id}`, () => runtime2.unloadAgent(agent.id, { mode: "immediate" }))));
133494
- let stoppedAgents = agents.length - failed.filter((name15) => name15.startsWith("agent ")).length;
134083
+ let listedAgents = 0;
134084
+ for (let pass = 0; pass < 2; pass++) {
134085
+ if (pass > 0) {
134086
+ let loading = await runtime2.settleLoads();
134087
+ loading.length > 0 && failed.push(...loading.map((filePath) => `still loading: ${filePath}`));
134088
+ }
134089
+ let agents = runtime2.listAgents();
134090
+ listedAgents += agents.length, await Promise.all(agents.map((agent) => step(`agent ${agent.id}`, () => runtime2.unloadAgent(agent.id, { mode: "immediate" }))));
134091
+ }
134092
+ let left = runtime2.listAgents();
134093
+ left.length > 0 && failed.push(...left.map((agent) => `still loaded: ${agent.id}`));
134094
+ let stoppedAgents = listedAgents - failed.filter((name15) => name15.startsWith("agent ")).length;
133495
134095
  if (await step("ws connections", () => options.wsService?.stopAll?.()), await step("mesh server", async () => {
133496
134096
  deps.meshServer ? await deps.meshServer.stop() : skipped.push("mesh server");
133497
134097
  }), await step("child processes", () => killAllTracked()), await step("host execs", () => killAllHostExecs()), await step("sandbox workers", () => {
@@ -133512,7 +134112,7 @@ function registerFleetRoutes(server, ctx) {
133512
134112
  ...skipped.length ? { skipped } : {},
133513
134113
  ...failed.length ? { failed } : {}
133514
134114
  };
133515
- });
134115
+ };
133516
134116
  }
133517
134117
 
133518
134118
  // src/main/daemon/routes/dashboard-routes.ts
@@ -134181,7 +134781,7 @@ function registerRegistryRoutes(server, ctx) {
134181
134781
  }), server.post("/registry/agents/:id/bring-home", async (request) => {
134182
134782
  let id = request.params.id, factory12 = ctx.options.agentFactory, result = await bringRegistryAgentHome(() => agentRegistry().resolveFile(id), {
134183
134783
  agentsFolder: () => {
134184
- let configured = settings2?.get("agentsFolder"), folder = factory12?.defaultDirectory() ?? (typeof configured == "string" && configured.trim() ? resolve17(configured.trim()) : join43(homedir7(), "Documents", "adf-agents"));
134784
+ let configured = settings2?.get("agentsFolder"), folder = factory12?.defaultDirectory() ?? (typeof configured == "string" && configured.trim() ? resolve17(configured.trim()) : join43(homedir7(), "Documents", defaultAgentsFolderName()));
134185
134785
  return mkdirSync17(folder, { recursive: !0 }), folder;
134186
134786
  },
134187
134787
  providers: settings2?.get("providers") ?? [],
@@ -137641,12 +138241,10 @@ var BASE_REVISION_INVALID = "baseRevision (or If-Match) must be a revision numbe
137641
138241
  function getOpenApiSpec() {
137642
138242
  return openapi_default;
137643
138243
  }
137644
- var SETTINGS_SECRET_KEYS = ["ownerMnemonic", "runtimePrivateKey", "runtimeEncPrivateKey", "mcpOauthCredentials"], SUPERSEDED_MNEMONIC_PREFIX = "ownerMnemonicSuperseded:";
137645
- function isSecretSettingsKey(key) {
137646
- return SETTINGS_SECRET_KEYS.includes(key) || key.startsWith(SUPERSEDED_MNEMONIC_PREFIX);
137647
- }
137648
138244
  var SETTINGS_WRITE_DENY_KEYS = [
137649
138245
  ...SETTINGS_SECRET_KEYS,
138246
+ // How Studio stores those secrets: the user's own choice in Studio, never set over HTTP.
138247
+ SECRET_STORAGE_POLICY_KEY,
137650
138248
  "trustedDaemonEncKeys",
137651
138249
  // Owner/runtime identity: changed only through /identity (daemon) or Studio.
137652
138250
  "ownerDid",
@@ -138818,13 +139416,13 @@ function createDaemonHttpApi(runtime2, opts = {}) {
138818
139416
  }
138819
139417
  }), server.post("/agents/:id/start", async (request, reply) => {
138820
139418
  try {
138821
- let preflight = request.query?.preflight === "1" || request.query?.preflight === "true", hasUserMessage = request.query?.hasUserMessage === "1" || request.query?.hasUserMessage === "true", credentialsLocked;
139419
+ let killEpoch = RuntimeGate.killEpoch, preflight = request.query?.preflight === "1" || request.query?.preflight === "true", hasUserMessage = request.query?.hasUserMessage === "1" || request.query?.hasUserMessage === "true", credentialsLocked;
138822
139420
  if (preflight && !runtime2.getAgent(request.params.id)) {
138823
139421
  let check = await runtime2.preflightProvider(request.params.id);
138824
139422
  if (!check.ok) return reply.code(409).send({ error: check.error, code: check.code ?? "provider_check_failed" });
138825
139423
  credentialsLocked = check.credentialsLocked;
138826
139424
  }
138827
- let result = await runtime2.startOrLoadAgent(request.params.id, { hasUserMessage });
139425
+ let result = await runtime2.startOrLoadAgent(request.params.id, { hasUserMessage, killEpoch });
138828
139426
  return {
138829
139427
  success: !0,
138830
139428
  loaded: result.loaded,
@@ -138867,6 +139465,18 @@ function createDaemonHttpApi(runtime2, opts = {}) {
138867
139465
  } catch (err) {
138868
139466
  return handleRuntimeError(reply, err);
138869
139467
  }
139468
+ }), server.post("/agents/:id/commands/:name/run", async (request, reply) => {
139469
+ let { id, name: name15 } = request.params;
139470
+ if (!runtime2.getAgentOrDocument(id)) return notFound3(reply, `Unknown agent "${id}"`);
139471
+ if (!runtime2.getAgent(id)) return notRunning(reply, id);
139472
+ let body = request.body;
139473
+ if (body !== void 0 && (!isRecord4(body) || body.args !== void 0 && typeof body.args != "string" || body.loop !== void 0 && typeof body.loop != "string" || body.authorize !== void 0 && typeof body.authorize != "boolean" || body.requestId !== void 0 && typeof body.requestId != "string"))
139474
+ return reply.code(400).send({ error: "Command body must contain string args, loop and requestId, and a boolean authorize.", code: "invalid_request" });
139475
+ try {
139476
+ return await runtime2.runAgentCommand(id, name15, body ?? {});
139477
+ } catch (err) {
139478
+ return handleRuntimeError(reply, err);
139479
+ }
138870
139480
  }), server.post("/agents/:id/compact", async (request, reply) => {
138871
139481
  if (!runtime2.getAgentOrDocument(request.params.id)) return notFound3(reply, `Unknown agent "${request.params.id}"`);
138872
139482
  try {
@@ -139117,7 +139727,7 @@ async function listProviderModels(runtime2, settingsStore, providerId, agentId,
139117
139727
  return { provider: providerId, models: await listGrokSubscriptionModels(getGrokAuthManager()) };
139118
139728
  }
139119
139729
  if (cfg.type === "claude-subscription") {
139120
- let { listClaudeSubscriptionModels } = await import("./claude-subscription-33BMZB6I.mjs");
139730
+ let { listClaudeSubscriptionModels } = await import("./claude-subscription-EB4LAAVV.mjs");
139121
139731
  return { provider: providerId, models: await listClaudeSubscriptionModels() };
139122
139732
  }
139123
139733
  if (cfg.type === "anthropic") {
@@ -140088,6 +140698,8 @@ var CLIENT_SETTINGS_KEYS = [
140088
140698
  // Owner and runtime identity (§5). Key material never crosses HTTP
140089
140699
  // (SETTINGS_SECRET_KEYS, SETTINGS_WRITE_DENY_KEYS in daemon/http-api.ts).
140090
140700
  "ownerMnemonic",
140701
+ // How Studio stores the secrets above (src/main/services/secret-storage-policy.ts).
140702
+ "secretStoragePolicy",
140091
140703
  "ownerDid",
140092
140704
  "ownerDidSeedDerived",
140093
140705
  "ownerEncPublicKey",
@@ -141039,7 +141651,7 @@ process.on("uncaughtException", (err) => {
141039
141651
  process.on("unhandledRejection", (reason) => {
141040
141652
  console.error("[ADF Daemon] Unhandled rejection:", reason instanceof Error ? reason.stack : reason);
141041
141653
  });
141042
- var port = Number(process.env.ADF_DAEMON_PORT ?? 7385), host = process.env.ADF_DAEMON_HOST ?? "127.0.0.1", pidFile = process.env.ADF_DAEMON_PIDFILE, settingsPath = process.env.ADF_DAEMON_SETTINGS ?? defaultSettingsPath(), daemonVersion = "0.8.0", startedBy = takeLaunchEnv();
141654
+ var port = Number(process.env.ADF_DAEMON_PORT ?? 7385), host = process.env.ADF_DAEMON_HOST ?? "127.0.0.1", pidFile = process.env.ADF_DAEMON_PIDFILE, settingsPath = process.env.ADF_DAEMON_SETTINGS ?? defaultSettingsPath(), daemonVersion = "0.8.2", startedBy = takeLaunchEnv();
141043
141655
  console.log(`[ADF Daemon] Version ${daemonVersion} (pid ${process.pid}, started by ${startedBy})`);
141044
141656
  var settings = new FileSettingsStore(settingsPath);
141045
141657
  processAgentLeases().configure({ kind: "daemon", settingsPath, version: daemonVersion });
@@ -141068,12 +141680,18 @@ if (behindProxy) {
141068
141680
  } catch (err) {
141069
141681
  noteLocalProofProblem(`the local proof file could not be written or protected: ${err instanceof Error ? err.message : String(err)}`), console.error("[ADF Daemon] Could not write or protect the local proof file; owner import (adf identity import) and the stored-value reveal routes stay unavailable:", err instanceof Error ? err.message : err);
141070
141682
  }
141071
- var ownerIdentity = new DaemonIdentity({
141072
- settings,
141073
- settingsPath,
141074
- encKey: daemonEncKey,
141075
- bootPassphrase: readBootPassphrase((path) => readFileSync14(path, "utf-8"))
141076
- });
141683
+ var ownerIdentity;
141684
+ try {
141685
+ ownerIdentity = new DaemonIdentity({
141686
+ settings,
141687
+ settingsPath,
141688
+ encKey: daemonEncKey,
141689
+ bootPassphrase: readBootPassphrase((path) => readFileSync14(path, "utf-8"))
141690
+ });
141691
+ } catch (err) {
141692
+ if (!(err instanceof SecretStoreSelectionError)) throw err;
141693
+ console.error(`[ADF Daemon] Owner secret store: ${err.message}`), process.exit(1);
141694
+ }
141077
141695
  settings.setProviderKeyVault(ownerIdentity.providerKeys);
141078
141696
  {
141079
141697
  let initial = ownerIdentity.refresh();