@sema-agent/cli 1.0.75 → 1.0.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/sema-main.js CHANGED
@@ -55924,10 +55924,11 @@ function structuredToToolUseResult(structured, modelText) {
55924
55924
  }
55925
55925
  ] : []) } } : null;
55926
55926
  case "agent": {
55927
- if (s.status !== "async_launched")
55927
+ let receipt = readAsyncLaunchedAgentReceipt(s);
55928
+ if (receipt === null)
55928
55929
  return null;
55929
- let agentId = typeof s.task_id == "string" ? s.task_id : void 0;
55930
- return agentId === void 0 || typeof s.description != "string" || typeof s.prompt != "string" ? null : (registerOutstandingBgTask(agentId, s.description, s.prompt, typeof s.seq == "number" ? s.seq : void 0), {
55930
+ let agentId = receipt.taskId;
55931
+ return typeof s.description != "string" || typeof s.prompt != "string" ? null : (registerOutstandingBgTask(agentId, s.description, s.prompt, receipt.seq), {
55931
55932
  toolUseResult: {
55932
55933
  isAsync: !0,
55933
55934
  status: "async_launched",
@@ -56047,6 +56048,18 @@ function reportFindingsToolUseResult(structured, rawInput) {
56047
56048
  }
56048
56049
  };
56049
56050
  }
56051
+ function readAsyncLaunchedAgentReceipt(structured) {
56052
+ if (structured === null || typeof structured != "object")
56053
+ return null;
56054
+ let s = structured;
56055
+ if (s.type !== "agent" || s.status !== "async_launched")
56056
+ return null;
56057
+ let taskId = s.task_id;
56058
+ return typeof taskId != "string" || taskId.length === 0 ? null : {
56059
+ taskId,
56060
+ ...typeof s.seq == "number" && Number.isFinite(s.seq) ? { seq: s.seq } : {}
56061
+ };
56062
+ }
56050
56063
  function readCompletedAgentCard(structured) {
56051
56064
  if (structured === null || typeof structured != "object")
56052
56065
  return null;
@@ -56471,6 +56484,26 @@ var labels, init_engineToolLabelStore = __esm({
56471
56484
  });
56472
56485
 
56473
56486
  // node_modules/@sema-agent/client-core/dist/subagentContentStore.js
56487
+ function coerceOutput(v2) {
56488
+ if (v2 != null) {
56489
+ if (typeof v2 == "string")
56490
+ return v2;
56491
+ if (Array.isArray(v2)) {
56492
+ let parts = [];
56493
+ for (let block2 of v2)
56494
+ if (block2 && typeof block2 == "object") {
56495
+ let b3 = block2;
56496
+ b3.type === "text" && typeof b3.text == "string" ? parts.push(b3.text) : b3.type === "image" && parts.push("[image]");
56497
+ }
56498
+ return parts.join("");
56499
+ }
56500
+ try {
56501
+ return JSON.stringify(v2);
56502
+ } catch {
56503
+ return String(v2);
56504
+ }
56505
+ }
56506
+ }
56474
56507
  function touchLru(map2, key) {
56475
56508
  let v2 = map2.get(key);
56476
56509
  v2 !== void 0 && (map2.delete(key), map2.set(key, v2));
@@ -56480,7 +56513,7 @@ function stateFor(taskId, parentToolCallId) {
56480
56513
  if (s)
56481
56514
  touchLru(tasks, taskId);
56482
56515
  else {
56483
- if (tasks.size >= 32) {
56516
+ if (tasks.size >= MAX_TASKS) {
56484
56517
  let oldest = tasks.keys().next().value;
56485
56518
  oldest !== void 0 && tasks.delete(oldest);
56486
56519
  }
@@ -56489,7 +56522,7 @@ function stateFor(taskId, parentToolCallId) {
56489
56522
  return s;
56490
56523
  }
56491
56524
  function pushItem(s, item) {
56492
- s.items.push(item), s.items.length > 200 && (s.items.splice(0, s.items.length - 200), s.openTools.clear(), s.items.forEach((it2, i) => {
56525
+ s.items.push(item), s.items.length > MAX_ITEMS_PER_TASK && (s.items.splice(0, s.items.length - MAX_ITEMS_PER_TASK), s.openTools.clear(), s.items.forEach((it2, i) => {
56493
56526
  it2.kind === "tool" && it2.output === void 0 && s.openTools.set(it2.id, i);
56494
56527
  }));
56495
56528
  }
@@ -56506,14 +56539,20 @@ function scheduleNotify(taskId) {
56506
56539
  notifyListener(id);
56507
56540
  } catch {
56508
56541
  }
56509
- }, 250));
56542
+ }, NOTIFY_COALESCE_MS));
56510
56543
  }
56511
- function registerSubagentAlias(parentToolCallId, taskId) {
56544
+ function registerSubagentContentAlias(parentToolCallId, taskId) {
56545
+ aliasContentKey(parentToolCallId, taskId);
56546
+ }
56547
+ function aliasContentKey(parentToolCallId, taskId) {
56512
56548
  if (!parentToolCallId || !taskId || parentToolCallId === taskId)
56513
- return;
56514
- parentToTask.set(parentToolCallId, taskId), taskToParent.set(taskId, parentToolCallId), pendingNotify.delete(parentToolCallId) && pendingNotify.add(taskId);
56549
+ return !1;
56550
+ parentToTask.set(parentToolCallId, taskId), pendingNotify.delete(parentToolCallId) && pendingNotify.add(taskId);
56515
56551
  let parked = tasks.get(parentToolCallId);
56516
- parked && !tasks.has(taskId) && (tasks.delete(parentToolCallId), tasks.set(taskId, parked), scheduleNotify(taskId));
56552
+ return parked && !tasks.has(taskId) && (tasks.delete(parentToolCallId), tasks.set(taskId, parked), scheduleNotify(taskId)), !0;
56553
+ }
56554
+ function registerSubagentAlias(parentToolCallId, taskId) {
56555
+ aliasContentKey(parentToolCallId, taskId) && taskToParent.set(taskId, parentToolCallId);
56517
56556
  }
56518
56557
  function canonicalKey(ev) {
56519
56558
  return ev.taskId !== ev.parentToolCallId ? ev.taskId : parentToTask.get(ev.parentToolCallId) ?? ev.parentToolCallId;
@@ -56589,7 +56628,7 @@ function clearSubagentContent(taskId) {
56589
56628
  }
56590
56629
  function recordBgTerminalFacts(taskId, facts2) {
56591
56630
  if (taskId) {
56592
- if (bgFacts.size >= 32 && !bgFacts.has(taskId)) {
56631
+ if (bgFacts.size >= MAX_TASKS && !bgFacts.has(taskId)) {
56593
56632
  let oldest = bgFacts.keys().next().value;
56594
56633
  oldest !== void 0 && bgFacts.delete(oldest);
56595
56634
  }
@@ -56602,7 +56641,7 @@ function getBgTerminalFacts(taskId) {
56602
56641
  }
56603
56642
  function recordBgParentRun(taskId, runId) {
56604
56643
  if (!(!taskId || !runId || taskId === runId)) {
56605
- if (bgParentRun.size >= 64 && !bgParentRun.has(taskId)) {
56644
+ if (bgParentRun.size >= MAX_TASKS * 2 && !bgParentRun.has(taskId)) {
56606
56645
  let oldest = bgParentRun.keys().next().value;
56607
56646
  oldest !== void 0 && bgParentRun.delete(oldest);
56608
56647
  }
@@ -56632,9 +56671,9 @@ function subscribeSubagentContent(fn2) {
56632
56671
  notifyListener === fn2 && (notifyListener = null);
56633
56672
  };
56634
56673
  }
56635
- var tasks, parentToTask, taskToParent, notifyListener, pendingNotify, notifyTimer, bgFacts, bgParentRun, ownEngineRuns, init_subagentContentStore = __esm({
56674
+ var MAX_ITEMS_PER_TASK, MAX_TASKS, NOTIFY_COALESCE_MS, tasks, parentToTask, taskToParent, notifyListener, pendingNotify, notifyTimer, bgFacts, bgParentRun, ownEngineRuns, init_subagentContentStore = __esm({
56636
56675
  "node_modules/@sema-agent/client-core/dist/subagentContentStore.js"() {
56637
- tasks = /* @__PURE__ */ new Map(), parentToTask = /* @__PURE__ */ new Map(), taskToParent = /* @__PURE__ */ new Map(), notifyListener = null, pendingNotify = /* @__PURE__ */ new Set(), notifyTimer = null;
56676
+ MAX_ITEMS_PER_TASK = 200, MAX_TASKS = 32, NOTIFY_COALESCE_MS = 250, tasks = /* @__PURE__ */ new Map(), parentToTask = /* @__PURE__ */ new Map(), taskToParent = /* @__PURE__ */ new Map(), notifyListener = null, pendingNotify = /* @__PURE__ */ new Set(), notifyTimer = null;
56638
56677
  bgFacts = /* @__PURE__ */ new Map();
56639
56678
  bgParentRun = /* @__PURE__ */ new Map();
56640
56679
  ownEngineRuns = /* @__PURE__ */ new Set();
@@ -56650,6 +56689,7 @@ var assistantArm, userArm, systemArm, diagnosticsArm, steeringInjectedArm, works
56650
56689
  init_diagnostics();
56651
56690
  init_engineToolLabelStore();
56652
56691
  init_subagentContentStore();
56692
+ init_toolResult();
56653
56693
  init_workflow();
56654
56694
  init_ids();
56655
56695
  init_wireShapes();
@@ -56818,7 +56858,11 @@ var assistantArm, userArm, systemArm, diagnosticsArm, steeringInjectedArm, works
56818
56858
  list.length > 0 && (yield chrome({ kind: "prompt_suggestions", laneProof: MAIN, suggestions: list }));
56819
56859
  }, toolEndResultArm = function* (m2, { ctx, idOf, cards, panel }) {
56820
56860
  let callId = typeof m2.toolCallId == "string" ? m2.toolCallId : void 0;
56821
- recordEngineToolLabel(callId, m2.label), callId !== void 0 && (yield* panel.settle(callId, m2.isError === !0, m2.output !== void 0 ? flattenWireOutput(m2.output) : void 0));
56861
+ if (recordEngineToolLabel(callId, m2.label), callId !== void 0) {
56862
+ let launched = readAsyncLaunchedAgentReceipt(m2.structured);
56863
+ launched !== null && registerSubagentContentAlias(callId, launched.taskId);
56864
+ }
56865
+ callId !== void 0 && (yield* panel.settle(callId, m2.isError === !0, m2.output !== void 0 ? flattenWireOutput(m2.output) : void 0));
56822
56866
  let p = callId !== void 0 ? cards.peek(callId) : void 0;
56823
56867
  if (p === void 0 || callId === void 0)
56824
56868
  return;
@@ -58120,15 +58164,15 @@ function wireAuthTokenFor(baseUrl, token) {
58120
58164
  }
58121
58165
  function makeEngineWireClient(cfg) {
58122
58166
  try {
58123
- return new AgentClient({
58167
+ let base = {
58124
58168
  baseUrl: cfg.baseUrl,
58125
- authToken: resolveWireAuth(cfg.baseUrl, cfg.token),
58126
58169
  // F-011 停发:缺席/空串=不给键(SDK 6.11 缺席=不发 x-agent-principal 头,owner-null)。
58127
58170
  ...cfg.principal !== void 0 && cfg.principal !== "" ? { principal: cfg.principal } : {},
58128
58171
  ...cfg.timeoutMs !== void 0 ? { timeoutMs: cfg.timeoutMs } : {},
58129
58172
  maxRetries: cfg.maxRetries ?? 0,
58130
58173
  ...cfg.fetchImpl ? { fetch: cfg.fetchImpl } : {}
58131
- });
58174
+ };
58175
+ return typeof cfg.token == "object" ? new AgentClient({ ...base, authToken: cfg.token }) : new AgentClient({ ...base, authToken: resolveWireAuth(cfg.baseUrl, cfg.token) });
58132
58176
  } catch {
58133
58177
  return null;
58134
58178
  }
@@ -59805,14 +59849,24 @@ async function* runStreamInner(events3, ctx, handle2 = {}) {
59805
59849
  let sub = ev;
59806
59850
  publishSubagentContentEvent({
59807
59851
  type: sub.type,
59808
- // taskId is on every subagent event (EventIdentity); parentToolCallId keys the fallback
59852
+ // 🔴 EventIdentity 上**没有** `taskId`(sdk events.d.ts 的 `interface EventIdentity` 只有
59853
+ // eventId / parentToolCallId,加 LIVE 白名单四臂的 sourceTaskId / bgAgentId;`taskId` 只长在
59854
+ // `meta` 首帧)⇒ 生产上这里恒走右臂,归账键由 store 的 `parentToTask` 补(canonicalKey)。
59855
+ // `??` 保留是**容将来**:哪天上游真在内容帧上发 taskId,这条直接认,不必改形。
59856
+ // (0.30.0 发包扫描订正:此前这行注释写「taskId is on every subagent event」,与 store 侧
59857
+ // `canonicalKey` 的注释互相矛盾,且被 .d.ts 直接证伪。)
59809
59858
  taskId: sub.taskId ?? sub.parentToolCallId,
59810
59859
  parentToolCallId: sub.parentToolCallId,
59811
59860
  delta: sub.delta,
59812
59861
  toolCallId: sub.toolCallId,
59813
59862
  toolName: sub.toolName,
59814
59863
  args: sub.args,
59815
- output: typeof sub.output == "string" ? sub.output : void 0,
59864
+ // #158 移交①([3674](d) 姊妹病,2026-08-12):此前是 `typeof sub.output === 'string' ?
59865
+ // sub.output : undefined` —— 而 wire 的 `tool_end.output` 是非均匀的(块数组形合法),
59866
+ // 于是子代 lane 的块数组 output 经本臂进内容账本**恒空**(查看态卡有工具、结果栏永远空白)。
59867
+ // 换用 store 自己的那个唯一字符串化口(tail 腿 engineSubagentTail 用的同一份):两条腿喂
59868
+ // 同一个账本,字符串化口就不能有第二份。
59869
+ output: coerceOutput(sub.output),
59816
59870
  isError: sub.isError
59817
59871
  });
59818
59872
  continue;
@@ -59883,7 +59937,31 @@ var MAIN2, reportedDroppedTypes, DROPPED_TYPE_MEMO_CAP, DROPPED_TYPE_DISPLAY_CAP
59883
59937
  }
59884
59938
  });
59885
59939
 
59940
+ // node_modules/@sema-agent/client-core/dist/abortableSleep.js
59941
+ function abortableSleep(ms, signal) {
59942
+ return signal.aborted ? Promise.resolve() : new Promise((resolve57) => {
59943
+ let timer2 = setTimeout(() => {
59944
+ signal.removeEventListener("abort", onAbort), resolve57();
59945
+ }, ms), onAbort = () => {
59946
+ clearTimeout(timer2), resolve57();
59947
+ };
59948
+ signal.addEventListener("abort", onAbort, { once: !0 });
59949
+ });
59950
+ }
59951
+ var init_abortableSleep = __esm({
59952
+ "node_modules/@sema-agent/client-core/dist/abortableSleep.js"() {
59953
+ }
59954
+ });
59955
+
59886
59956
  // node_modules/@sema-agent/client-core/dist/adapter/activeRunSelfHeal.js
59957
+ function askParkForeignGateKind(row2) {
59958
+ let kind = typeof row2.gateKind == "string" && row2.gateKind.length > 0 ? row2.gateKind : null;
59959
+ return kind !== null && !ASK_PARK_GATE_KINDS.includes(kind) ? kind : null;
59960
+ }
59961
+ function sessionOpts(deps2) {
59962
+ let sessionId = deps2?.sessionId;
59963
+ return typeof sessionId == "string" && sessionId.length > 0 ? { session: sessionId } : {};
59964
+ }
59887
59965
  function describeFailure(e) {
59888
59966
  if (typeof e == "object" && e !== null && "message" in e) {
59889
59967
  let m2 = e.message;
@@ -59899,6 +59977,64 @@ function readStatus(record2) {
59899
59977
  function reopenDelivered(verdict) {
59900
59978
  return verdict.reopened === !0 && verdict.presented !== !1;
59901
59979
  }
59980
+ function atMostOnceFailureClass(e) {
59981
+ let status3 = e?.status;
59982
+ return typeof status3 == "number" && status3 >= 400 && status3 < 500 ? "rejected" : "unknown";
59983
+ }
59984
+ function readSteerDelivery(receipt) {
59985
+ if (typeof receipt != "object" || receipt === null || !("delivery" in receipt))
59986
+ return null;
59987
+ let d4 = receipt.delivery;
59988
+ return typeof d4 == "string" && d4.length > 0 ? d4 : null;
59989
+ }
59990
+ function readSteerReceiptStatus(receipt) {
59991
+ if (typeof receipt != "object" || receipt === null || !("status" in receipt))
59992
+ return null;
59993
+ let s = receipt.status;
59994
+ return typeof s == "string" && s.length > 0 ? s : null;
59995
+ }
59996
+ function claimPollSleep(ms, signal) {
59997
+ return abortableSleep(ms, signal ?? new AbortController().signal);
59998
+ }
59999
+ function claimProbeLease(caller, remainingMs) {
60000
+ let ctl = new AbortController(), timer2 = setTimeout(() => {
60001
+ ctl.abort(new Error("probe deadline"));
60002
+ }, Math.max(1, remainingMs)), onCaller = () => {
60003
+ ctl.abort(caller?.reason);
60004
+ };
60005
+ return caller?.aborted === !0 ? onCaller() : caller?.addEventListener("abort", onCaller, { once: !0 }), {
60006
+ signal: ctl.signal,
60007
+ release: () => {
60008
+ clearTimeout(timer2), caller?.removeEventListener("abort", onCaller);
60009
+ }
60010
+ };
60011
+ }
60012
+ async function waitForClaimRelease(taskId, deps2) {
60013
+ let now2 = typeof deps2.now == "function" ? deps2.now : () => Date.now(), sleep10 = typeof deps2.sleep == "function" ? deps2.sleep : claimPollSleep, budgetMs = Number.isFinite(deps2.budgetMs) && deps2.budgetMs > 0 ? deps2.budgetMs : 0, startedAt = now2(), deadline = startedAt + budgetMs, waited = () => now2() - startedAt, isAborted3 = () => deps2.signal?.aborted === !0, delay = CANCEL_POLL_START_MS, lastStatus = null;
60014
+ for (; ; ) {
60015
+ if (isAborted3())
60016
+ return { released: !1, waitedMs: waited(), aborted: !0, lastStatus };
60017
+ let remaining = deadline - now2();
60018
+ if (remaining <= 0)
60019
+ return { released: !1, waitedMs: waited(), aborted: !1, lastStatus };
60020
+ if (await sleep10(Math.min(delay, remaining), deps2.signal), isAborted3())
60021
+ return { released: !1, waitedMs: waited(), aborted: !0, lastStatus };
60022
+ delay = Math.min(Math.round(delay * 1.5), CANCEL_POLL_MAX_MS);
60023
+ let status3, lease = claimProbeLease(deps2.signal, deadline - now2());
60024
+ try {
60025
+ status3 = readStatus(await deps2.get(taskId, {
60026
+ signal: lease.signal,
60027
+ ...deps2.session !== void 0 ? { session: deps2.session } : {}
60028
+ }));
60029
+ } catch {
60030
+ if (lastStatus = null, lease.release(), isAborted3())
60031
+ return { released: !1, waitedMs: waited(), aborted: !0, lastStatus };
60032
+ continue;
60033
+ }
60034
+ if (lease.release(), lastStatus = status3, status3 !== null && CLAIM_RELEASED_STATES.includes(status3))
60035
+ return { released: !0, waitedMs: waited(), aborted: !1, lastStatus: status3 };
60036
+ }
60037
+ }
59902
60038
  async function attemptActiveRunSelfHeal(signal, runs, deps2) {
59903
60039
  let pending2 = !1;
59904
60040
  try {
@@ -59916,7 +60052,7 @@ async function attemptActiveRunSelfHeal(signal, runs, deps2) {
59916
60052
  detail: "the engine did not name the run that holds this session"
59917
60053
  };
59918
60054
  let gateKind2 = signal.pendingGate?.kind ?? null;
59919
- if (gateKind2 === PLAN_REVIEW_GATE_KIND)
60055
+ if (gateKind2 !== null && PLAN_REVIEW_GATE_KINDS.includes(gateKind2))
59920
60056
  return planReviewArm(taskId, signal, deps2);
59921
60057
  if (gateKind2 !== null && ASK_PARK_GATE_KINDS.includes(gateKind2))
59922
60058
  return askParkArm(taskId, signal, deps2);
@@ -59926,31 +60062,95 @@ async function attemptActiveRunSelfHeal(signal, runs, deps2) {
59926
60062
  return { kind: "state-unknown", taskId, detail: "this client exposes no durable run verbs" };
59927
60063
  let record2;
59928
60064
  try {
59929
- record2 = await runs.get(taskId);
60065
+ record2 = await runs.get(taskId, sessionOpts(deps2));
59930
60066
  } catch (e) {
59931
60067
  return { kind: "state-unknown", taskId, detail: describeFailure(e) };
59932
60068
  }
59933
60069
  status3 = readStatus(record2);
59934
60070
  }
59935
- return status3 === null ? { kind: "state-unknown", taskId, detail: "the engine reported no status for that run" } : gateKind2 !== null ? { kind: "not-parked", taskId, status: status3 } : PLAN_REVIEW_STATES.includes(status3) ? planReviewArm(taskId, signal, deps2) : ASK_PARK_STATES.includes(status3) ? askParkArm(taskId, signal, deps2) : { kind: "not-parked", taskId, status: status3 };
60071
+ return status3 === null ? { kind: "state-unknown", taskId, detail: "the engine reported no status for that run" } : gateKind2 !== null ? { kind: "not-parked", taskId, status: status3 } : PLAN_REVIEW_STATES.includes(status3) ? planReviewArm(taskId, signal, deps2) : ASK_PARK_STATES.includes(status3) ? askParkArm(taskId, signal, deps2) : RUNNING_STATES.includes(status3) ? runningChoiceArm(taskId, status3, runs, deps2) : { kind: "not-parked", taskId, status: status3 };
60072
+ }
60073
+ async function planVerdict(taskId, deps2) {
60074
+ try {
60075
+ return await deps2?.reopenPlanReview?.(taskId) ?? { reopened: !1 };
60076
+ } catch {
60077
+ return { reopened: !1 };
60078
+ }
59936
60079
  }
59937
- function planReviewArm(taskId, signal, deps2) {
59938
- let verdict = { reopened: !1 };
60080
+ async function askVerdict(taskId, deps2) {
59939
60081
  try {
59940
- verdict = deps2?.reopenPlanReview?.(taskId) ?? { reopened: !1 };
60082
+ return await deps2?.reopenAskPark?.(taskId) ?? { reopened: !1 };
59941
60083
  } catch {
59942
- verdict = { reopened: !1 };
60084
+ return { reopened: !1 };
59943
60085
  }
60086
+ }
60087
+ async function planReviewArm(taskId, signal, deps2) {
60088
+ let verdict = await planVerdict(taskId, deps2);
59944
60089
  return reopenDelivered(verdict) ? { kind: "plan-review-reopened", taskId, firstSight: verdict.firstSight === !0 } : { kind: "plan-review-reopen-failed", taskId, decidePath: signal.pendingGate?.decidePath ?? null };
59945
60090
  }
59946
60091
  async function askParkArm(taskId, signal, deps2) {
59947
- let verdict = { reopened: !1 };
60092
+ let verdict = await askVerdict(taskId, deps2);
60093
+ return reopenDelivered(verdict) ? { kind: "ask-reopened", taskId, firstSight: verdict.firstSight === !0 } : { kind: "ask-reopen-failed", taskId, decidePath: signal.pendingGate?.decidePath ?? null };
60094
+ }
60095
+ async function runningChoiceArm(taskId, status3, runs, deps2) {
60096
+ let notParked = { kind: "not-parked", taskId, status: status3 }, offer = deps2?.offerRunningChoice;
60097
+ if (typeof offer != "function")
60098
+ return notParked;
60099
+ let text2 = typeof deps2?.deniedMessage == "string" ? deps2.deniedMessage : "", canSteer = typeof runs?.steer == "function" && text2.length > 0, canCancel = typeof runs?.cancel == "function" && typeof runs?.get == "function";
60100
+ if (!canSteer && !canCancel)
60101
+ return notParked;
60102
+ let choice = "wait";
59948
60103
  try {
59949
- verdict = await deps2?.reopenAskPark?.(taskId) ?? { reopened: !1 };
60104
+ choice = await offer({ taskId, status: status3, canSteer, canCancel }) ?? "wait";
59950
60105
  } catch {
59951
- verdict = { reopened: !1 };
60106
+ choice = "wait";
59952
60107
  }
59953
- return reopenDelivered(verdict) ? { kind: "ask-reopened", taskId, firstSight: verdict.firstSight === !0 } : { kind: "ask-reopen-failed", taskId, decidePath: signal.pendingGate?.decidePath ?? null };
60108
+ if (choice === "steer" && canSteer && runs?.steer !== void 0)
60109
+ try {
60110
+ let receipt = await runs.steer(taskId, { text: text2 }, sessionOpts(deps2)), delivery = readSteerDelivery(receipt), receiptStatus = readSteerReceiptStatus(receipt);
60111
+ if (delivery === "queued") {
60112
+ let verdict = receiptStatus !== null && PLAN_REVIEW_STATES.includes(receiptStatus) ? await planVerdict(taskId, deps2) : receiptStatus !== null && ASK_PARK_STATES.includes(receiptStatus) ? await askVerdict(taskId, deps2) : null;
60113
+ return { kind: "running-steered", taskId, delivery, status: receiptStatus, reopened: verdict };
60114
+ }
60115
+ return { kind: "running-steered", taskId, delivery, status: receiptStatus, reopened: null };
60116
+ } catch (e) {
60117
+ return { kind: "running-steer-failed", taskId, detail: describeFailure(e), delivery: atMostOnceFailureClass(e) };
60118
+ }
60119
+ if (choice === "cancel" && canCancel && runs?.cancel !== void 0 && runs.get !== void 0) {
60120
+ let durable = runs, durableGet = runs.get, boundGet = (id, opts) => durableGet.call(durable, id, opts), cancelFailure = null, cancelWaitMs = deps2?.cancelReleaseWaitMs ?? CANCEL_RELEASE_WAIT_MS, cancelStartedAt = Date.now(), cancelLease = claimProbeLease(deps2?.signal, cancelWaitMs);
60121
+ try {
60122
+ await durable.cancel?.(taskId, { signal: cancelLease.signal, ...sessionOpts(deps2) });
60123
+ } catch (e) {
60124
+ if (deps2?.signal?.aborted === !0)
60125
+ return {
60126
+ kind: "running-cancel-timeout",
60127
+ taskId,
60128
+ waitedMs: Date.now() - cancelStartedAt,
60129
+ aborted: !0,
60130
+ confirmedHeld: !1
60131
+ };
60132
+ let delivery = atMostOnceFailureClass(e);
60133
+ if (delivery === "rejected")
60134
+ return { kind: "running-cancel-failed", taskId, detail: describeFailure(e), delivery };
60135
+ cancelFailure = { detail: describeFailure(e), delivery };
60136
+ } finally {
60137
+ cancelLease.release();
60138
+ }
60139
+ let verdict = await waitForClaimRelease(taskId, {
60140
+ get: boundGet,
60141
+ budgetMs: cancelWaitMs,
60142
+ ...deps2?.signal !== void 0 ? { signal: deps2.signal } : {},
60143
+ ...sessionOpts(deps2)
60144
+ });
60145
+ return verdict.released ? { kind: "running-cancelled", taskId } : cancelFailure !== null ? { kind: "running-cancel-failed", taskId, ...cancelFailure } : {
60146
+ kind: "running-cancel-timeout",
60147
+ taskId,
60148
+ waitedMs: verdict.waitedMs,
60149
+ aborted: verdict.aborted,
60150
+ confirmedHeld: verdict.lastStatus !== null && CLAIM_HELD_STATES.includes(verdict.lastStatus)
60151
+ };
60152
+ }
60153
+ return notParked;
59954
60154
  }
59955
60155
  function gateKindPhrase(kind) {
59956
60156
  return kind ? `a ${kind} decision` : "a decision";
@@ -59994,6 +60194,27 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut) {
59994
60194
  let viaEngine = outcome.decidePath ? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.` : "";
59995
60195
  return `The previous turn is parked waiting for a plan review (run ${outcome.taskId}) and sema could not reopen that approval card. It did NOT cancel the run \u2014 that would have discarded the plan for you. Your message was NOT sent; ${wayOut} if you no longer want that plan.${viaEngine}`;
59996
60196
  }
60197
+ // ── 三选卡的四种动作结局(选 ③ / 没答 / 没呈上都落 not-parked)────────────────────────────
60198
+ case "running-steered": {
60199
+ let handle2 = `run ${outcome.taskId}`, textOnly = "Only the TEXT of your message was handed over \u2014 a steer carries no attachments, so any images in that submission were NOT sent.";
60200
+ if (outcome.delivery === "applied")
60201
+ return `sema handed your message to the run that is already working (${handle2}) \u2014 the engine applied it to that run, so it is picked up at that run's next step. ${textOnly} Nothing was cancelled, and your message did NOT start a new turn \u2014 watch that run for what it does with it.`;
60202
+ if (outcome.delivery === "queued") {
60203
+ let parked = outcome.status ? `parked (status ${outcome.status})` : "parked", next = outcome.reopened !== null && reopenDelivered(outcome.reopened) ? "sema has surfaced that decision card \u2014 answering it is what lets that run resume and pick your message up." : `sema could not surface that decision card here, so nothing will resume that run until that decision is made; ${wayOut} if you no longer want it.`;
60204
+ return `${handle2} is not actually working right now: the engine reports it ${parked} on a decision it needs from you, so it queued your message on that park instead of running it. ${next} ${textOnly} Nothing was cancelled, and your message did NOT start a new turn.`;
60205
+ }
60206
+ return outcome.delivery === "parked_for_wake" ? `${handle2} had already finished, so the engine parked your message on that finished run instead of running it \u2014 a parked message is delivered only if this session is woken on the engine (POST /v1/sessions/:id/wake), which sema does not do here. That run is no longer holding this session: send your message again to run it as a new turn \u2014 but note that the parked copy is NOT discarded, so if anything ever wakes this session on the engine that instruction would run a second time. ${textOnly} Nothing was cancelled.` : `sema handed your message to ${handle2}, but the engine did not say how it will be delivered${outcome.status ? ` (it reported that run as ${outcome.status})` : ""}. ${textOnly} Nothing was cancelled, and your message did NOT start a new turn \u2014 watch that run before sending it again.`;
60207
+ }
60208
+ case "running-steer-failed":
60209
+ return outcome.delivery === "rejected" ? `The engine rejected handing your message to the running run (run ${outcome.taskId}): ${outcome.detail}. sema did NOT retry it (a steer is not safe to send twice) and it did NOT cancel that run. Your message was NOT sent; send it again if you still want it.` : `sema sent your message to the running run (run ${outcome.taskId}) but could not confirm what happened to it (${outcome.detail}). A steer is not safe to send twice, so sema did NOT retry it, and it did NOT cancel that run. That run may or may not have received your message \u2014 watch what it does next before sending it again.`;
60210
+ case "running-cancelled":
60211
+ return `You chose to cancel run ${outcome.taskId}. The engine confirmed it is no longer holding this session, so sema is sending your message now.`;
60212
+ case "running-cancel-timeout": {
60213
+ let waited = `${String(Math.max(1, Math.round(outcome.waitedMs / 1e3)))}s`;
60214
+ return outcome.aborted ? `sema asked the engine to cancel run ${outcome.taskId} and you interrupted while it was still confirming (${waited} in). Cancelling is asynchronous, so that run may still be winding down and may still hold this session. Your message was NOT sent; send it again in a moment, or ${wayOut}.` : outcome.confirmedHeld ? `sema asked the engine to cancel run ${outcome.taskId}, but that run still held this session ${waited} later \u2014 cancelling is asynchronous and it may still be winding down. Your message was NOT sent; send it again in a moment, or ${wayOut}.` : `sema asked the engine to cancel run ${outcome.taskId} but could not read that run's state back within ${waited}, so it cannot confirm whether the session was released. Your message was NOT sent; send it again in a moment (if that run really is gone it will just run), or ${wayOut}.`;
60215
+ }
60216
+ case "running-cancel-failed":
60217
+ return outcome.delivery === "unknown" ? `sema sent the cancel for run ${outcome.taskId} but could not confirm what happened to it (${outcome.detail}), and it could not read that run back as released either. That run may or may not still be holding this session. Your message was NOT sent; send it again in a moment, or ${wayOut}.` : `sema could not cancel run ${outcome.taskId} \u2014 the engine rejected that request (${outcome.detail}), so nothing was cancelled and sema cannot tell whether that run is still holding this session. Your message was NOT sent; send it again to find out, or ${wayOut}.`;
59997
60218
  case "not-parked":
59998
60219
  return signal?.pendingGate ? `This session is held by an earlier run (run ${outcome.taskId}, status ${outcome.status}) that is waiting on ${gateKindPhrase(signal.pendingGate.kind)} \u2014 it will not finish on its own. sema did NOT cancel it, because cancelling would decide that item for you. Your message was NOT sent; ${wayOut} if you no longer want it.` : `This session already has a run in flight (run ${outcome.taskId}, status ${outcome.status}) \u2014 most likely a turn you sent to the background. sema did NOT cancel it, because that would throw away work you asked for. Wait for it to finish, or ${wayOut}.`;
59999
60220
  case "state-unknown":
@@ -60014,9 +60235,12 @@ function activeRunBusyHeadlessRow(signal, copy2) {
60014
60235
  let startOver = `${freshSession.charAt(0).toUpperCase()}${freshSession.slice(1)}`;
60015
60236
  return `This session is locked by an earlier run${handle2} that was never released, so this message was NOT sent. Nothing here clears on its own. ${startOver}, or release the run on the engine: ${cancelPath}`;
60016
60237
  }
60017
- var PLAN_REVIEW_GATE_KIND, ASK_PARK_GATE_KINDS, PLAN_REVIEW_STATES, ASK_PARK_STATES, DEFAULT_WAY_OUT, DEFAULT_FRESH_SESSION, init_activeRunSelfHeal = __esm({
60238
+ var PLAN_REVIEW_GATE_KIND, PLAN_REVIEW_GATE_KINDS, ASK_PARK_GATE_KINDS, PLAN_REVIEW_STATES, ASK_PARK_STATES, RUNNING_STATES, CLAIM_RELEASED_STATES, CLAIM_HELD_STATES, CANCEL_RELEASE_WAIT_MS, CANCEL_POLL_START_MS, CANCEL_POLL_MAX_MS, DEFAULT_WAY_OUT, DEFAULT_FRESH_SESSION, init_activeRunSelfHeal = __esm({
60018
60239
  "node_modules/@sema-agent/client-core/dist/adapter/activeRunSelfHeal.js"() {
60019
- PLAN_REVIEW_GATE_KIND = "plan_review", ASK_PARK_GATE_KINDS = ["human", "irreversible_ask", "tool_approval"], PLAN_REVIEW_STATES = ["needs_review"], ASK_PARK_STATES = ["suspended"];
60240
+ init_abortableSleep();
60241
+ PLAN_REVIEW_GATE_KIND = "plan_review", PLAN_REVIEW_GATE_KINDS = [PLAN_REVIEW_GATE_KIND, "dry_run_review"], ASK_PARK_GATE_KINDS = ["human", "irreversible_ask", "policy_ask", "tool_approval"];
60242
+ PLAN_REVIEW_STATES = ["needs_review"], ASK_PARK_STATES = ["suspended"], RUNNING_STATES = ["running"], CLAIM_RELEASED_STATES = ["completed", "failed", "blocked", "timeout"], CLAIM_HELD_STATES = ["running", "suspended", "needs_review"];
60243
+ CANCEL_RELEASE_WAIT_MS = 1e4, CANCEL_POLL_START_MS = 200, CANCEL_POLL_MAX_MS = 2e3;
60020
60244
  DEFAULT_WAY_OUT = "run /clear to keep working in a fresh session", DEFAULT_FRESH_SESSION = "start a new session (drop --resume/--continue)";
60021
60245
  }
60022
60246
  });
@@ -61538,17 +61762,6 @@ function installSubagentActivitySink(sink2) {
61538
61762
  activitySink = prev;
61539
61763
  };
61540
61764
  }
61541
- function coerceOutput(v2) {
61542
- if (v2 != null) {
61543
- if (typeof v2 == "string")
61544
- return v2;
61545
- try {
61546
- return JSON.stringify(v2);
61547
- } catch {
61548
- return String(v2);
61549
- }
61550
- }
61551
- }
61552
61765
  function isEngineSubagentTailActive(taskId) {
61553
61766
  return activeTails.has(taskId);
61554
61767
  }
@@ -61713,22 +61926,6 @@ var init_workflowMonitor = __esm({
61713
61926
  }
61714
61927
  });
61715
61928
 
61716
- // node_modules/@sema-agent/client-core/dist/abortableSleep.js
61717
- function abortableSleep(ms, signal) {
61718
- return signal.aborted ? Promise.resolve() : new Promise((resolve57) => {
61719
- let timer2 = setTimeout(() => {
61720
- signal.removeEventListener("abort", onAbort), resolve57();
61721
- }, ms), onAbort = () => {
61722
- clearTimeout(timer2), resolve57();
61723
- };
61724
- signal.addEventListener("abort", onAbort, { once: !0 });
61725
- });
61726
- }
61727
- var init_abortableSleep = __esm({
61728
- "node_modules/@sema-agent/client-core/dist/abortableSleep.js"() {
61729
- }
61730
- });
61731
-
61732
61929
  // node_modules/@sema-agent/client-core/dist/workflowClient.js
61733
61930
  import { APIError as APIError2 } from "@sema-agent/sdk";
61734
61931
  function coerceRunStatus(s) {
@@ -63647,7 +63844,7 @@ async function findPendingForTask(client3, taskId, matches2, opts) {
63647
63844
  } catch (e) {
63648
63845
  return { ok: !1, reason: `approvals.list failed: ${String(e)}` };
63649
63846
  }
63650
- let pending2 = rows2.find((r) => r.taskId === taskId && matches2(typeof r.toolName == "string" ? r.toolName : void 0)) ?? rows2.find((r) => r.taskId === taskId);
63847
+ let decidable = rows2.filter((r) => askParkForeignGateKind(r) === null), pending2 = decidable.find((r) => r.taskId === taskId && matches2(typeof r.toolName == "string" ? r.toolName : void 0)) ?? decidable.find((r) => r.taskId === taskId);
63651
63848
  return pending2 ? { ok: !0, pending: pending2, gatedCallId: pending2.toolCallId ?? pending2.boundCallId ?? void 0 } : { ok: !1, reason: "no pending checkpoint for this run (resolved/expired?)", code: "no_pending" };
63652
63849
  }
63653
63850
  function makeHitlCanUseTool(bridge3, prompt) {
@@ -63688,6 +63885,7 @@ function backendDeny(reason) {
63688
63885
  }
63689
63886
  var DEFAULT_DENY_REASON, MAX_DENY_REASON_CHARS, HitlSafetyError, HitlBridge, init_hitlBridge = __esm({
63690
63887
  "node_modules/@sema-agent/client-core/dist/hitl/hitlBridge.js"() {
63888
+ init_activeRunSelfHeal();
63691
63889
  init_types4();
63692
63890
  init_host();
63693
63891
  DEFAULT_DENY_REASON = "The user rejected this tool use", MAX_DENY_REASON_CHARS = 4096;
@@ -64054,11 +64252,13 @@ async function surfaceFsApprovalAndDecide(deps2, taskId, argsByCall, signal) {
64054
64252
  let { pending: pending2, gatedCallId } = found, toolName2 = typeof pending2.toolName == "string" ? pending2.toolName : "", args = (gatedCallId !== void 0 ? argsByCall.get(gatedCallId) : void 0) ?? pending2.input ?? {};
64055
64253
  if (typeof args != "object" || args === null)
64056
64254
  return { kind: "failed", gatedCallId, reason: "gate has no tool input payload" };
64057
- let bridge3 = new HitlBridge(deps2.client, taskId), callKey = approvalCallKey(gatedCallId, taskId), card = await surfaceApprovalCard({
64255
+ let bridge3 = new HitlBridge(deps2.client, taskId), callKey = approvalCallKey(gatedCallId, taskId), ruleSuggestionsReadOnly = readRuleSuggestions(pending2.ruleSuggestions), card = await surfaceApprovalCard({
64058
64256
  toolName: toolName2,
64059
64257
  args,
64060
64258
  callKey,
64061
- ...signal ? { signal } : {}
64259
+ ...signal ? { signal } : {},
64260
+ ...pending2.governanceForced === !0 ? { governanceForced: !0 } : {},
64261
+ ...ruleSuggestionsReadOnly !== void 0 ? { ruleSuggestionsReadOnly } : {}
64062
64262
  });
64063
64263
  switch (card.kind) {
64064
64264
  case "failed":
@@ -64931,6 +65131,260 @@ var init_parkOwnership = __esm({
64931
65131
  }
64932
65132
  });
64933
65133
 
65134
+ // node_modules/@sema-agent/client-core/dist/hitl/parkRowBirthWait.js
65135
+ function parkRowPollDelay(ms, signal) {
65136
+ return abortableSleep(ms, signal ?? new AbortController().signal);
65137
+ }
65138
+ async function waitForParkRowBirth(deps2) {
65139
+ let now2 = typeof deps2.now == "function" ? deps2.now : () => Date.now(), sleep10 = typeof deps2.sleep == "function" ? deps2.sleep : parkRowPollDelay, budgetMs = Number.isFinite(deps2.budgetMs) && deps2.budgetMs > 0 ? deps2.budgetMs : 0, intervalMs = Number.isFinite(deps2.intervalMs) && deps2.intervalMs > 0 ? deps2.intervalMs : 1, startedAt = now2(), probes = 0, lastReason = "no decidable pending row for this park", aborted2 = () => deps2.signal?.aborted === !0, safeProbe = async (attempt, signal) => {
65140
+ try {
65141
+ return await deps2.probe(attempt, signal);
65142
+ } catch (e) {
65143
+ return { kind: "unborn", reason: `probe threw: ${String(e)}` };
65144
+ }
65145
+ }, probeWithinRemaining = async (attempt, remainingMs) => {
65146
+ if (budgetMs <= 0)
65147
+ return safeProbe(attempt, deps2.signal);
65148
+ let ctl = new AbortController(), timer2, deadline = new Promise((resolve57) => {
65149
+ timer2 = setTimeout(() => {
65150
+ ctl.abort(), resolve57("deadline");
65151
+ }, Math.max(1, remainingMs));
65152
+ }), caller = deps2.signal, abortProbeLane = () => {
65153
+ try {
65154
+ ctl.abort();
65155
+ } catch {
65156
+ }
65157
+ }, onAbort, externalAbort = new Promise((resolve57) => {
65158
+ if (caller !== void 0) {
65159
+ if (caller.aborted) {
65160
+ abortProbeLane(), resolve57("aborted");
65161
+ return;
65162
+ }
65163
+ onAbort = () => {
65164
+ abortProbeLane(), resolve57("aborted");
65165
+ };
65166
+ try {
65167
+ caller.addEventListener("abort", onAbort, { once: !0 });
65168
+ } catch {
65169
+ onAbort = void 0;
65170
+ }
65171
+ }
65172
+ }), merged = caller !== void 0 && typeof AbortSignal.any == "function" ? AbortSignal.any([caller, ctl.signal]) : ctl.signal;
65173
+ try {
65174
+ return await Promise.race([safeProbe(attempt, merged), deadline, externalAbort]);
65175
+ } finally {
65176
+ if (timer2 !== void 0 && clearTimeout(timer2), onAbort !== void 0 && caller !== void 0)
65177
+ try {
65178
+ caller.removeEventListener("abort", onAbort);
65179
+ } catch {
65180
+ }
65181
+ }
65182
+ };
65183
+ for (; ; ) {
65184
+ if (aborted2())
65185
+ return { kind: "aborted", waitedMs: now2() - startedAt, probes };
65186
+ probes += 1;
65187
+ let outcome = await probeWithinRemaining(probes, budgetMs - (now2() - startedAt));
65188
+ if (outcome === "aborted" || aborted2())
65189
+ return { kind: "aborted", waitedMs: now2() - startedAt, probes };
65190
+ if (outcome === "deadline")
65191
+ return {
65192
+ kind: "unborn",
65193
+ reason: `${lastReason} (the last read face did not answer within the remaining window)`,
65194
+ waitedMs: now2() - startedAt,
65195
+ probes
65196
+ };
65197
+ let probed = outcome;
65198
+ if (probed.kind === "row")
65199
+ return { kind: "row", row: probed.row, waitedMs: now2() - startedAt, probes };
65200
+ if (probed.kind === "settled")
65201
+ return { kind: "settled", reason: probed.reason, waitedMs: now2() - startedAt, probes };
65202
+ lastReason = probed.reason;
65203
+ let remainingMs = budgetMs - (now2() - startedAt);
65204
+ if (remainingMs <= 0)
65205
+ return { kind: "unborn", reason: lastReason, waitedMs: now2() - startedAt, probes };
65206
+ if (await sleep10(Math.min(intervalMs, remainingMs), deps2.signal), aborted2())
65207
+ return { kind: "aborted", waitedMs: now2() - startedAt, probes };
65208
+ }
65209
+ }
65210
+ var init_parkRowBirthWait = __esm({
65211
+ "node_modules/@sema-agent/client-core/dist/hitl/parkRowBirthWait.js"() {
65212
+ init_abortableSleep();
65213
+ }
65214
+ });
65215
+
65216
+ // node_modules/@sema-agent/client-core/dist/hitl/approvalDecisionNoteAudit.js
65217
+ function cleanNote(raw2) {
65218
+ let flat = raw2.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").replace(/ {2,}/g, " ").trim();
65219
+ return flat.length <= 200 ? flat : `${flat.slice(0, 199)}\u2026`;
65220
+ }
65221
+ function readDecisionNoteAudit(ack) {
65222
+ if (ack === null || typeof ack != "object")
65223
+ return { state: "unknown" };
65224
+ let o = ack, note = typeof o.decisionNote == "string" && o.decisionNote.trim() !== "" ? cleanNote(o.decisionNote) : void 0;
65225
+ return o.noteRecorded === !1 ? { state: "not-recorded", ...note !== void 0 ? { note } : {} } : o.noteRecorded === !0 ? { state: "recorded", ...note !== void 0 ? { note } : {} } : note !== void 0 ? { state: "recorded", note } : { state: "unknown" };
65226
+ }
65227
+ function decisionNoteAuditLine(audit, opts) {
65228
+ if (audit.state === "unknown")
65229
+ return null;
65230
+ let quoted = audit.note !== void 0 ? `: "${audit.note}"` : "";
65231
+ return opts?.settledElsewhere === !0 ? audit.state === "recorded" ? `this approval was already decided elsewhere \u2014 the reason recorded on the audit trail${quoted}` : `this approval was already decided elsewhere; your reason was not saved to the audit trail \u2014 that decision stands${quoted}` : audit.state === "recorded" ? `decision reason recorded on the audit trail${quoted}` : `your decision stands \u2014 the engine did not save its reason to the audit trail${quoted}`;
65232
+ }
65233
+ var DECISION_NOTE_NOTICE_KEY, init_approvalDecisionNoteAudit = __esm({
65234
+ "node_modules/@sema-agent/client-core/dist/hitl/approvalDecisionNoteAudit.js"() {
65235
+ DECISION_NOTE_NOTICE_KEY = "approval-decision-note";
65236
+ }
65237
+ });
65238
+
65239
+ // node_modules/@sema-agent/client-core/dist/hitl/askParkRowRouting.js
65240
+ function classifyAskParkRows(rows2, taskId, deps2) {
65241
+ let isOwned = deps2?.isOwned ?? ((row2) => pendingRowIsOwnedByThisSession(row2, deps2?.ownership)), own2 = rows2.find((r) => r.taskId === taskId), sole = rows2.length === 1 ? rows2[0] : void 0, soleUnowned = sole !== void 0 && !isOwned(sole), pending2 = own2 ?? (soleUnowned ? void 0 : sole);
65242
+ if (pending2 === void 0)
65243
+ return {
65244
+ kind: "unborn",
65245
+ reason: rows2.length === 0 ? (
65246
+ // 空表两义:要么真孤儿 park(座位占着却没有待决行 = 引擎态异常),要么下一只门的行还没
65247
+ // 铸出来。两义分不开 ⇒ 按可修的那一义有界重查,窗尽才按前者如实收口。
65248
+ `no pending row at all for task ${taskId} \u2014 either an orphan park (a seat held with no decision outstanding) or the next gate's row is not minted yet`
65249
+ ) : soleUnowned ? `the only pending row (task ${String(sole?.taskId)} / session ${String(sole?.sessionId)}) could not be proven to belong to this client (not in its own-run ledger, session id absent or different) \u2014 refusing to surface an approval that may be another session's` : `${String(rows2.length)} pending rows but none for task ${taskId} \u2014 ambiguous queue, refusing to surface an unrelated row`
65250
+ };
65251
+ let foreignKind = askParkForeignGateKind(pending2);
65252
+ return foreignKind !== null ? {
65253
+ kind: "unborn",
65254
+ reason: `the visible pending row (task ${String(pending2.taskId)}) is parked on a '${foreignKind}' gate \u2014 not an ask/approval gate; this arm will not surface or decide it, and it may also be the previous gate's row still visible during a transition`
65255
+ } : { kind: "row", row: pending2 };
65256
+ }
65257
+ function askParkRowGateKind(row2) {
65258
+ return typeof row2.gateKind == "string" && row2.gateKind.length > 0 ? row2.gateKind : null;
65259
+ }
65260
+ function askParkRowIdentity(taskId, row2) {
65261
+ let rowTaskId = typeof row2.taskId == "string" && row2.taskId.length > 0 ? row2.taskId : taskId, gatedCallId = row2.toolCallId ?? row2.boundCallId ?? void 0;
65262
+ return { rowTaskId, gatedCallId, armedKey: approvalCallKey(gatedCallId, rowTaskId) };
65263
+ }
65264
+ function askParkRowArm(row2) {
65265
+ let rowGateKind = askParkRowGateKind(row2), toolNameIsAsk = isAskTool(row2.toolName ?? void 0);
65266
+ if (rowGateKind === null)
65267
+ return toolNameIsAsk ? "question" : "tool-gate";
65268
+ if (!GENERIC_DECISION_GATE_KINDS.includes(rowGateKind))
65269
+ return "tool-gate";
65270
+ let rowHasToolName = typeof row2.toolName == "string" && row2.toolName.trim().length > 0;
65271
+ return toolNameIsAsk || askQuestionsFromPending(row2) !== null || !rowHasToolName ? "question" : "tool-gate";
65272
+ }
65273
+ function askParkRowStillPending(rows2, identity3) {
65274
+ return rows2.some((r) => identity3.gatedCallId !== void 0 ? (r.toolCallId ?? r.boundCallId ?? void 0) === identity3.gatedCallId : r.taskId === identity3.rowTaskId);
65275
+ }
65276
+ function classifyAskParkChainFailure(i) {
65277
+ return i.rowStillPending ? "retry" : i.cardPresented ? "row-gone-after-card" : "row-unborn";
65278
+ }
65279
+ function createRowArmSingleFlight() {
65280
+ let inFlight4 = /* @__PURE__ */ new Map(), starting = /* @__PURE__ */ new Set();
65281
+ return {
65282
+ join: (key, start) => {
65283
+ if (starting.has(key))
65284
+ return Promise.reject(new Error(`row-arm single flight: start() re-entered join() for its own key while starting (${key})`));
65285
+ let joined = inFlight4.get(key);
65286
+ if (joined !== void 0)
65287
+ return joined;
65288
+ let settle2, fail6, run2 = new Promise((resolve57, reject3) => {
65289
+ settle2 = resolve57, fail6 = reject3;
65290
+ });
65291
+ inFlight4.set(key, run2);
65292
+ let release2 = () => {
65293
+ inFlight4.get(key) === run2 && inFlight4.delete(key);
65294
+ };
65295
+ run2.then(release2, release2), starting.add(key);
65296
+ try {
65297
+ let started2 = start();
65298
+ started2 === run2 ? fail6?.(new Error("row-arm single flight: start() returned the in-flight placeholder for the same key")) : settle2?.(started2);
65299
+ } catch (e) {
65300
+ fail6?.(e);
65301
+ } finally {
65302
+ starting.delete(key);
65303
+ }
65304
+ return run2;
65305
+ },
65306
+ size: () => inFlight4.size,
65307
+ clear: () => {
65308
+ inFlight4.clear();
65309
+ }
65310
+ };
65311
+ }
65312
+ var ASK_PARK_ROW_WAIT_MS, ASK_PARK_ROW_POLL_MS, GENERIC_DECISION_GATE_KINDS, init_askParkRowRouting = __esm({
65313
+ "node_modules/@sema-agent/client-core/dist/hitl/askParkRowRouting.js"() {
65314
+ init_activeRunSelfHeal();
65315
+ init_frameRouter();
65316
+ init_gateIdentity();
65317
+ init_parkOwnership();
65318
+ ASK_PARK_ROW_WAIT_MS = 15e3, ASK_PARK_ROW_POLL_MS = 500;
65319
+ GENERIC_DECISION_GATE_KINDS = ["human", "policy_ask"];
65320
+ }
65321
+ });
65322
+
65323
+ // node_modules/@sema-agent/client-core/dist/hitl/resumeRunningCard.js
65324
+ function resumeRunningLivenessRow(msSinceLastActivity) {
65325
+ let ms = msSinceLastActivity;
65326
+ return typeof ms != "number" || !Number.isFinite(ms) || ms < 0 ? null : `The engine last recorded activity on it ${String(Math.round(ms / 1e3))}s ago.`;
65327
+ }
65328
+ function resumeRunningQuestionText(i) {
65329
+ let head = `This conversation was resumed while an earlier turn is still executing on the engine (run ${i.taskId}, status ${i.status}). sema did not touch it. What should it do?`, liveness = resumeRunningLivenessRow(i.msSinceLastActivity);
65330
+ return liveness === null ? head : `${head}
65331
+ ${liveness}`;
65332
+ }
65333
+ function resumeRunningOptions(i) {
65334
+ let options = [
65335
+ {
65336
+ // 🔴 默认焦点 —— 三条路里唯一零副作用的一条,所以它是手滑回车的落点。
65337
+ // 🔴 措辞:这一项**不许**承诺「它会留在某个任务面板里」—— 这张卡的前提恰恰是「那条 run 活得
65338
+ // 比上一个客户端进程久」,新进程里没有任何腿会为它建行(③ 的处置本身也是显式零动作)。
65339
+ // 默认焦点上的一句假 affordance 是最坏的一格:手滑回车的人拿到一条既看不见、也停不掉、
65340
+ // 还占着会话锁的 run。换成两句**可证的**真话:本屏不会列出它;它还占着会话,所以下一条
65341
+ // 消息会再撞上它(那时 409 三选卡会带着 steer/cancel 两条真出路出现)。
65342
+ label: RESUME_CHOICE_BACKGROUND_LABEL,
65343
+ description: "Leaves the run alone. It keeps executing on the engine, but this screen will not show it and it will not be listed here \u2014 your next message runs into it again, and sema asks you then."
65344
+ }
65345
+ ];
65346
+ return i.canAttach && options.push({
65347
+ label: RESUME_CHOICE_ATTACH_LABEL,
65348
+ // 🔴 第二句是**知情同意**那一半(见 {@link resumeAttachReplayDisclosure}):resume 入口没有
65349
+ // 事件锚可用,durable 尾只能从这条 run 的第一帧读起,所以崩溃前已经落盘并被 resume 还原到
65350
+ // 屏上的那一段会再出现一次。用户按下之前就得看见这句 —— 一个会做出用户没预料到的事的
65351
+ // 选项,和一个按了没用的选项同样是假 affordance。
65352
+ description: "Follows that run from here: sema replays it from the engine ledger and keeps streaming it into this conversation. Anything from that turn already shown above will appear a second time \u2014 sema has no resume marker for it."
65353
+ }), i.canCancel && options.push({
65354
+ label: RESUME_CHOICE_CANCEL_LABEL,
65355
+ description: "Stops that run on the engine (whatever it has been doing is discarded) and frees this session for a new turn."
65356
+ }), options;
65357
+ }
65358
+ function resumeChoiceFromLabels(selected) {
65359
+ if (!Array.isArray(selected) || selected.length !== 1)
65360
+ return "background";
65361
+ let label = selected[0];
65362
+ return label === RESUME_CHOICE_ATTACH_LABEL ? "attach" : label === RESUME_CHOICE_CANCEL_LABEL ? "cancel" : "background";
65363
+ }
65364
+ function resumeRunningNoUiGuidance(i) {
65365
+ let sid = typeof i.sessionId == "string" && i.sessionId.length > 0 ? i.sessionId : null, attach = sid === null ? "sema --resume (pick this session from the list)" : `sema --resume ${sid}`, T3 = RESUME_RUNNING_GUIDANCE_TAG;
65366
+ return [
65367
+ `${T3} run ${i.taskId} from an earlier turn is still executing on the engine, and this lane has no card to ask you on. sema did NOT cancel it and did NOT attach to it.`,
65368
+ `${T3} attach=${attach}`,
65369
+ `${T3} cancel=POST /v1/runs/${i.taskId}/cancel`,
65370
+ // 🔴 这条车道连 REPL 都不挂载,更没有任何本地面板可看。给的是引擎侧真读面。
65371
+ `${T3} background=do nothing \u2014 run ${i.taskId} keeps executing on the engine; this client does not list it (read it with GET /v1/runs/${i.taskId})`
65372
+ ].join(`
65373
+ `);
65374
+ }
65375
+ function resumeAttachReplayDisclosure(taskId) {
65376
+ return `sema: attaching to run ${taskId} \u2014 sema is replaying it from the engine's ledger, so anything from that turn already shown above will appear again below. There is no resume marker for a run that outlived its shell. The replayed turns are not billed into this session's cost again either \u2014 the engine's own record (GET /v1/runs/${taskId}) is the authority on what that run really spent.`;
65377
+ }
65378
+ function resumeRunningCancelUnconfirmedRow(taskId) {
65379
+ return `sema: the cancel for run ${taskId} did not come back confirmed, so that run may still be executing. This screen does not list it \u2014 read its real state on the engine (GET /v1/runs/${taskId}) before assuming this session is free.`;
65380
+ }
65381
+ var RESUME_CHOICE_ATTACH_LABEL, RESUME_CHOICE_CANCEL_LABEL, RESUME_CHOICE_BACKGROUND_LABEL, RESUME_CHOICE_HEADER, RESUME_RUNNING_NO_UI_EXIT_CODE, RESUME_RUNNING_GUIDANCE_TAG, init_resumeRunningCard = __esm({
65382
+ "node_modules/@sema-agent/client-core/dist/hitl/resumeRunningCard.js"() {
65383
+ RESUME_CHOICE_ATTACH_LABEL = "Attach and watch", RESUME_CHOICE_CANCEL_LABEL = "Cancel it", RESUME_CHOICE_BACKGROUND_LABEL = "Leave it running in the background", RESUME_CHOICE_HEADER = "Earlier run still in flight";
65384
+ RESUME_RUNNING_NO_UI_EXIT_CODE = 75, RESUME_RUNNING_GUIDANCE_TAG = "sema: resume-running:";
65385
+ }
65386
+ });
65387
+
64934
65388
  // node_modules/@sema-agent/client-core/dist/hitl/approvalsFeed.js
64935
65389
  function digestOf(rows2) {
64936
65390
  return rows2.map((r) => [r.sessionId ?? "", r.taskId ?? "", r.toolCallId ?? "", r.boundCallId ?? "", r.boundInputHash ?? "", r.toolName ?? ""].join("")).sort().join("");
@@ -65293,6 +65747,16 @@ function toolPermissionRequestIdDomain(requestId) {
65293
65747
  if (typeof requestId == "string")
65294
65748
  return TOOL_PERMISSION_REQUEST_ID_DOMAINS.find((d4) => requestId.startsWith(d4));
65295
65749
  }
65750
+ function toolPermissionRequestId(domain2, id) {
65751
+ if (!TOOL_PERMISSION_REQUEST_ID_DOMAINS.includes(domain2))
65752
+ throw new Error(`toolPermissionRequestId: \u672A\u767B\u8BB0\u7684 requestId \u57DF \u2014\u2014 \u53EA\u8BA4 ${TOOL_PERMISSION_REQUEST_ID_DOMAINS.join(" / ")}(\u57DF\u524D\u7F00\u5C31\u662F\u51B3\u65AD\u7684\u8DEF\u7531\u4F9D\u636E,\u57DF\u5916\u952E\u65E0\u5904\u53EF\u53BB)`);
65753
+ if (typeof id != "string" || id.length === 0)
65754
+ throw new Error(`toolPermissionRequestId: \u57DF ${domain2} \u7684 id \u5FC5\u987B\u662F\u975E\u7A7A\u4E32(\u6536\u5230 ${typeof id})\u2014\u2014 \u534A\u622A\u952E\u4F1A\u88AB\u8BFB\u53E3\u8BA4\u6210\u672C\u57DF,\u5374\u8DEF\u7531\u4E0D\u5230\u4EFB\u4F55\u76EE\u6807`);
65755
+ let nested2 = toolPermissionRequestIdDomain(id);
65756
+ if (nested2 !== void 0)
65757
+ throw new Error(`toolPermissionRequestId: \u57DF ${domain2} \u7684 id \u672C\u8EAB\u5DF2\u5E26\u57DF\u524D\u7F00 ${nested2} \u2014\u2014 \u628A\u94F8\u597D\u7684 requestId \u5F53 id \u518D\u5582\u8FDB\u6765\u4F1A\u5F97\u5230\u53CC\u524D\u7F00\u952E(durable \u7684 id \u6BB5\u53EF\u4EE5\u542B\u5192\u53F7,\u4F46\u4E0D\u80FD\u4EE5\u57DF\u524D\u7F00\u5F00\u5934)`);
65758
+ return `${domain2}${id}`;
65759
+ }
65296
65760
  function isLocalSessionRecord(s) {
65297
65761
  return checkLocalSessionRecord(s) === !0;
65298
65762
  }
@@ -66547,6 +67011,8 @@ __export(dist_exports, {
66547
67011
  ADAPTER_COVERAGE: () => ADAPTER_COVERAGE,
66548
67012
  ADAPTER_DIVERGENCES: () => ADAPTER_DIVERGENCES,
66549
67013
  ASK_PARK_GATE_KINDS: () => ASK_PARK_GATE_KINDS,
67014
+ ASK_PARK_ROW_POLL_MS: () => ASK_PARK_ROW_POLL_MS,
67015
+ ASK_PARK_ROW_WAIT_MS: () => ASK_PARK_ROW_WAIT_MS,
66550
67016
  ASK_PARK_STATES: () => ASK_PARK_STATES,
66551
67017
  ATTACHMENTS_DEFAULT_ON_KEYS: () => ATTACHMENTS_DEFAULT_ON_KEYS,
66552
67018
  ATTACHMENTS_ENV: () => ATTACHMENTS_ENV,
@@ -66559,6 +67025,7 @@ __export(dist_exports, {
66559
67025
  CANCEL_DENY_BUDGET_MS: () => CANCEL_DENY_BUDGET_MS,
66560
67026
  CANCEL_DENY_WARN_TEXT: () => CANCEL_DENY_WARN_TEXT,
66561
67027
  CANCEL_MESSAGE: () => CANCEL_MESSAGE,
67028
+ CANCEL_RELEASE_WAIT_MS: () => CANCEL_RELEASE_WAIT_MS,
66562
67029
  CATALOG_CACHE_RELATIVE_PATH: () => CATALOG_CACHE_RELATIVE_PATH,
66563
67030
  CATALOG_CACHE_STALE_MS: () => CATALOG_CACHE_STALE_MS,
66564
67031
  CATALOG_DEFAULT_HOSTS: () => CATALOG_DEFAULT_HOSTS,
@@ -66568,6 +67035,8 @@ __export(dist_exports, {
66568
67035
  CC_STOP_SEMANTICS_MIN_SERVER: () => CC_STOP_SEMANTICS_MIN_SERVER,
66569
67036
  CHROME_ARMS: () => CHROME_ARMS,
66570
67037
  CHROME_EVENT_KINDS: () => CHROME_EVENT_KINDS,
67038
+ CLAIM_HELD_STATES: () => CLAIM_HELD_STATES,
67039
+ CLAIM_RELEASED_STATES: () => CLAIM_RELEASED_STATES,
66571
67040
  CLASSIFIER_DENY_SIGNATURE: () => CLASSIFIER_DENY_SIGNATURE,
66572
67041
  CLIENT_VERBS: () => CLIENT_VERBS,
66573
67042
  CONFIG_LIMIT_INVALID: () => CONFIG_LIMIT_INVALID,
@@ -66581,6 +67050,7 @@ __export(dist_exports, {
66581
67050
  ControlSafetyError: () => ControlSafetyError,
66582
67051
  DEADLINE_SEC_MAX: () => DEADLINE_SEC_MAX,
66583
67052
  DEADLINE_SEC_MIN: () => DEADLINE_SEC_MIN,
67053
+ DECISION_NOTE_NOTICE_KEY: () => DECISION_NOTE_NOTICE_KEY,
66584
67054
  DEFAULT_CATALOG_SOURCES: () => DEFAULT_CATALOG_SOURCES,
66585
67055
  DEFAULT_CATALOG_TIMEOUT_MS: () => DEFAULT_CATALOG_TIMEOUT_MS,
66586
67056
  DEFAULT_DENY_REASON: () => DEFAULT_DENY_REASON,
@@ -66669,6 +67139,7 @@ __export(dist_exports, {
66669
67139
  PERMISSION_MODE_INTENTS: () => PERMISSION_MODE_INTENTS,
66670
67140
  PLAN_REVIEW_APPROVE_LABEL: () => PLAN_REVIEW_APPROVE_LABEL,
66671
67141
  PLAN_REVIEW_GATE_KIND: () => PLAN_REVIEW_GATE_KIND,
67142
+ PLAN_REVIEW_GATE_KINDS: () => PLAN_REVIEW_GATE_KINDS,
66672
67143
  PLAN_REVIEW_QUESTION_ID_PREFIX: () => PLAN_REVIEW_QUESTION_ID_PREFIX,
66673
67144
  PLAN_REVIEW_REJECT_LABEL: () => PLAN_REVIEW_REJECT_LABEL,
66674
67145
  PLAN_REVIEW_STATES: () => PLAN_REVIEW_STATES,
@@ -66681,8 +67152,15 @@ __export(dist_exports, {
66681
67152
  REMEMBER_NOT_APPLIED_WARN_TEXT: () => REMEMBER_NOT_APPLIED_WARN_TEXT,
66682
67153
  REOPEN_ID_TAIL: () => REOPEN_ID_TAIL,
66683
67154
  REQUEST_FIELD_MATRIX: () => REQUEST_FIELD_MATRIX,
67155
+ RESUME_CHOICE_ATTACH_LABEL: () => RESUME_CHOICE_ATTACH_LABEL,
67156
+ RESUME_CHOICE_BACKGROUND_LABEL: () => RESUME_CHOICE_BACKGROUND_LABEL,
67157
+ RESUME_CHOICE_CANCEL_LABEL: () => RESUME_CHOICE_CANCEL_LABEL,
67158
+ RESUME_CHOICE_HEADER: () => RESUME_CHOICE_HEADER,
67159
+ RESUME_RUNNING_GUIDANCE_TAG: () => RESUME_RUNNING_GUIDANCE_TAG,
67160
+ RESUME_RUNNING_NO_UI_EXIT_CODE: () => RESUME_RUNNING_NO_UI_EXIT_CODE,
66684
67161
  RETAIN_BACKGROUND_ENV: () => RETAIN_BACKGROUND_ENV,
66685
67162
  REWIND_ERROR_CODE_PREFIXES: () => REWIND_ERROR_CODE_PREFIXES,
67163
+ RUNNING_STATES: () => RUNNING_STATES,
66686
67164
  SANDBOX_CASCADE_CONFLICT_400_ANCHOR: () => SANDBOX_CASCADE_CONFLICT_400_ANCHOR,
66687
67165
  SANDBOX_K8S_ONLY_400_ANCHOR: () => SANDBOX_K8S_ONLY_400_ANCHOR,
66688
67166
  SANDBOX_NO_IMAGE_INDEX_400_ANCHOR: () => SANDBOX_NO_IMAGE_INDEX_400_ANCHOR,
@@ -66785,8 +67263,13 @@ __export(dist_exports, {
66785
67263
  armedKeyFromQuestionId: () => armedKeyFromQuestionId,
66786
67264
  asModelFallbackReason: () => asModelFallbackReason,
66787
67265
  askGateQuestionId: () => askGateQuestionId,
67266
+ askParkForeignGateKind: () => askParkForeignGateKind,
67267
+ askParkRowArm: () => askParkRowArm,
67268
+ askParkRowIdentity: () => askParkRowIdentity,
67269
+ askParkRowStillPending: () => askParkRowStillPending,
66788
67270
  askQuestionsFromPending: () => askQuestionsFromPending,
66789
67271
  assistantTaskToBackgroundRow: () => assistantTaskToBackgroundRow,
67272
+ atMostOnceFailureClass: () => atMostOnceFailureClass,
66790
67273
  attachmentsForRequest: () => attachmentsForRequest,
66791
67274
  attemptActiveRunSelfHeal: () => attemptActiveRunSelfHeal,
66792
67275
  awaitTaskAgentsWire: () => awaitTaskAgentsWire,
@@ -66804,6 +67287,8 @@ __export(dist_exports, {
66804
67287
  ccStopSemanticsFromVersion: () => ccStopSemanticsFromVersion,
66805
67288
  classifierDenyFromToolEnd: () => classifierDenyFromToolEnd,
66806
67289
  classifierDenyNoticeText: () => classifierDenyNoticeText,
67290
+ classifyAskParkChainFailure: () => classifyAskParkChainFailure,
67291
+ classifyAskParkRows: () => classifyAskParkRows,
66807
67292
  classifyHookNoticeFrame: () => classifyHookNoticeFrame,
66808
67293
  classifyTaskStopConflict: () => classifyTaskStopConflict,
66809
67294
  clearAllRetainedFleetRows: () => clearAllRetainedFleetRows,
@@ -66831,8 +67316,10 @@ __export(dist_exports, {
66831
67316
  createBackgroundView: () => createBackgroundView,
66832
67317
  createFleetLedger: () => createFleetLedger,
66833
67318
  createLiveWorkflowSource: () => createLiveWorkflowSource,
67319
+ createRowArmSingleFlight: () => createRowArmSingleFlight,
66834
67320
  createWireToCcAdapter: () => createWireToCcAdapter,
66835
67321
  decidePlanReview: () => decidePlanReview,
67322
+ decisionNoteAuditLine: () => decisionNoteAuditLine,
66836
67323
  defaultMaxTokensFor: () => defaultMaxTokensFor,
66837
67324
  deferToolsForRequest: () => deferToolsForRequest,
66838
67325
  degradedToolResultBody: () => degradedToolResultBody,
@@ -67070,9 +67557,13 @@ __export(dist_exports, {
67070
67557
  publishQuestionFrameFor: () => publishQuestionFrameFor,
67071
67558
  publishSubagentContentEvent: () => publishSubagentContentEvent,
67072
67559
  pushSubagentLocalEcho: () => pushSubagentLocalEcho,
67560
+ readAsyncLaunchedAgentReceipt: () => readAsyncLaunchedAgentReceipt,
67073
67561
  readCompletedAgentCard: () => readCompletedAgentCard,
67562
+ readDecisionNoteAudit: () => readDecisionNoteAudit,
67074
67563
  readEngineActiveBgTasks: () => readEngineActiveBgTasks,
67075
67564
  readEngineActiveBgTasksFor: () => readEngineActiveBgTasksFor,
67565
+ readSteerDelivery: () => readSteerDelivery,
67566
+ readSteerReceiptStatus: () => readSteerReceiptStatus,
67076
67567
  readToolApprovalRespondAck: () => readToolApprovalRespondAck,
67077
67568
  recordBgParentRun: () => recordBgParentRun,
67078
67569
  recordBgTerminalFacts: () => recordBgTerminalFacts,
@@ -67089,6 +67580,7 @@ __export(dist_exports, {
67089
67580
  registerOutstandingBgTask: () => registerOutstandingBgTask,
67090
67581
  registerOutstandingWorkflowRun: () => registerOutstandingWorkflowRun,
67091
67582
  registerSubagentAlias: () => registerSubagentAlias,
67583
+ registerSubagentContentAlias: () => registerSubagentContentAlias,
67092
67584
  renderTaskNotificationXml: () => renderTaskNotificationXml,
67093
67585
  reopenPlanReviewCard: () => reopenPlanReviewCard,
67094
67586
  reportFindingsToolUseResult: () => reportFindingsToolUseResult,
@@ -67110,6 +67602,13 @@ __export(dist_exports, {
67110
67602
  resolveWebSearch: () => resolveWebSearch,
67111
67603
  resolveWireAuth: () => resolveWireAuth,
67112
67604
  respondToQuestion: () => respondToQuestion,
67605
+ resumeAttachReplayDisclosure: () => resumeAttachReplayDisclosure,
67606
+ resumeChoiceFromLabels: () => resumeChoiceFromLabels,
67607
+ resumeRunningCancelUnconfirmedRow: () => resumeRunningCancelUnconfirmedRow,
67608
+ resumeRunningLivenessRow: () => resumeRunningLivenessRow,
67609
+ resumeRunningNoUiGuidance: () => resumeRunningNoUiGuidance,
67610
+ resumeRunningOptions: () => resumeRunningOptions,
67611
+ resumeRunningQuestionText: () => resumeRunningQuestionText,
67113
67612
  retainBackgroundFromEnv: () => retainBackgroundFromEnv,
67114
67613
  rewindSpecForMode: () => rewindSpecForMode,
67115
67614
  rowIdTail: () => rowIdTail,
@@ -67175,6 +67674,7 @@ __export(dist_exports, {
67175
67674
  toolEndResultToUserFrame: () => toolEndResultToUserFrame,
67176
67675
  toolNameIsFsWrite: () => toolNameIsFsWrite,
67177
67676
  toolNameIsShellExec: () => toolNameIsShellExec,
67677
+ toolPermissionRequestId: () => toolPermissionRequestId,
67178
67678
  toolPermissionRequestIdDomain: () => toolPermissionRequestIdDomain,
67179
67679
  turnEndUsage: () => turnEndUsage,
67180
67680
  turnUsageToModelUsage: () => turnUsageToModelUsage,
@@ -67189,6 +67689,8 @@ __export(dist_exports, {
67189
67689
  validateWebSearchChoice: () => validateWebSearchChoice,
67190
67690
  versionSupportsDetach: () => versionSupportsDetach,
67191
67691
  versionSupportsLimits: () => versionSupportsLimits,
67692
+ waitForClaimRelease: () => waitForClaimRelease,
67693
+ waitForParkRowBirth: () => waitForParkRowBirth,
67192
67694
  wasGateArmed: () => wasGateArmed,
67193
67695
  wasGateArmedFor: () => wasGateArmedFor,
67194
67696
  webSearchFromEnv: () => webSearchFromEnv,
@@ -67303,6 +67805,10 @@ var init_dist = __esm({
67303
67805
  init_gateIdentity();
67304
67806
  init_armedGateRegistry();
67305
67807
  init_parkOwnership();
67808
+ init_parkRowBirthWait();
67809
+ init_approvalDecisionNoteAudit();
67810
+ init_askParkRowRouting();
67811
+ init_resumeRunningCard();
67306
67812
  init_approvalsFeed();
67307
67813
  init_compensations();
67308
67814
  init_printNotification();
@@ -80955,12 +81461,14 @@ function settingsJsonTheme() {
80955
81461
  try {
80956
81462
  let v2 = resolveUserSetting("theme");
80957
81463
  if (typeof v2 == "string") return v2;
80958
- } catch {
81464
+ } catch (e) {
81465
+ failOpen("theme-source-user-setting", void 0, String(e));
80959
81466
  }
80960
81467
  try {
80961
81468
  let raw2 = readFileSync10(join31(homedir14(), ".sema", "settings.json"), "utf8"), parsed = JSON.parse(raw2);
80962
81469
  if (typeof parsed.theme == "string") return parsed.theme;
80963
- } catch {
81470
+ } catch (e) {
81471
+ failOpen("theme-source-settings-file", void 0, String(e));
80964
81472
  }
80965
81473
  }
80966
81474
  function resolveThemeSetting(setting) {
@@ -80973,7 +81481,8 @@ function semaInitialTheme() {
80973
81481
  function persistThemeToSettings(setting) {
80974
81482
  try {
80975
81483
  updateSettingsForSource("userSettings", { theme: setting }), resetUserSettingCache();
80976
- } catch {
81484
+ } catch (e) {
81485
+ failOpen("theme-write-user-setting", void 0, String(e));
80977
81486
  }
80978
81487
  }
80979
81488
  function detectFromColorFgBg() {
@@ -80989,6 +81498,7 @@ var cachedSystemTheme, init_systemTheme = __esm({
80989
81498
  "build-src/src/utils/systemTheme.ts"() {
80990
81499
  init_settings2();
80991
81500
  init_userSetting();
81501
+ init_failOpen();
80992
81502
  }
80993
81503
  });
80994
81504
 
@@ -114247,7 +114757,7 @@ function readReceiptStatus(receipt) {
114247
114757
  return typeof s == "string" && s.length > 0 ? s : null;
114248
114758
  }
114249
114759
  function cancelReleaseWaitMs() {
114250
- return cancelReleaseWaitOverrideMs ?? CANCEL_RELEASE_WAIT_MS;
114760
+ return cancelReleaseWaitOverrideMs ?? CANCEL_RELEASE_WAIT_MS2;
114251
114761
  }
114252
114762
  function sleep2(ms, signal) {
114253
114763
  return signal?.aborted === !0 ? Promise.resolve() : new Promise((resolve57) => {
@@ -114266,14 +114776,14 @@ function claimProbeSignal(caller, deadline) {
114266
114776
  let ac = new AbortController(), abort = (s) => ac.abort(s.reason);
114267
114777
  return caller.aborted ? abort(caller) : caller.addEventListener("abort", () => abort(caller), { once: !0 }), budget.addEventListener("abort", () => abort(budget), { once: !0 }), ac.signal;
114268
114778
  }
114269
- async function waitForClaimRelease(taskId, get4, budgetMs, signal) {
114270
- let startedAt = Date.now(), deadline = startedAt + budgetMs, waited = () => Date.now() - startedAt, isAborted3 = () => signal?.aborted === !0, delay = CANCEL_POLL_START_MS;
114779
+ async function waitForClaimRelease2(taskId, get4, budgetMs, signal) {
114780
+ let startedAt = Date.now(), deadline = startedAt + budgetMs, waited = () => Date.now() - startedAt, isAborted3 = () => signal?.aborted === !0, delay = CANCEL_POLL_START_MS2;
114271
114781
  for (; ; ) {
114272
114782
  if (isAborted3()) return { released: !1, waitedMs: waited(), aborted: !0 };
114273
114783
  let remaining = deadline - Date.now();
114274
114784
  if (remaining <= 0) return { released: !1, waitedMs: waited(), aborted: !1 };
114275
114785
  if (await sleep2(Math.min(delay, remaining), signal), isAborted3()) return { released: !1, waitedMs: waited(), aborted: !0 };
114276
- delay = Math.min(Math.round(delay * 1.5), CANCEL_POLL_MAX_MS);
114786
+ delay = Math.min(Math.round(delay * 1.5), CANCEL_POLL_MAX_MS2);
114277
114787
  let status3;
114278
114788
  try {
114279
114789
  status3 = readStatus2(await get4(taskId, { signal: claimProbeSignal(signal, deadline) }));
@@ -114282,7 +114792,7 @@ async function waitForClaimRelease(taskId, get4, budgetMs, signal) {
114282
114792
  if (e?.status === 404) return { released: !0, waitedMs: waited(), aborted: !1 };
114283
114793
  continue;
114284
114794
  }
114285
- if (status3 !== null && CLAIM_RELEASED_STATES.includes(status3)) return { released: !0, waitedMs: waited(), aborted: !1 };
114795
+ if (status3 !== null && CLAIM_RELEASED_STATES2.includes(status3)) return { released: !0, waitedMs: waited(), aborted: !1 };
114286
114796
  }
114287
114797
  }
114288
114798
  async function attemptActiveRunSelfHeal2(signal, runs, deps2) {
@@ -114292,6 +114802,11 @@ async function attemptActiveRunSelfHeal2(signal, runs, deps2) {
114292
114802
  } catch {
114293
114803
  pending2 = !1;
114294
114804
  }
114805
+ if (pending2 && deps2?.listOwnedPendingApprovals !== void 0)
114806
+ try {
114807
+ await deps2.listOwnedPendingApprovals() === 0 && (pending2 = !1);
114808
+ } catch {
114809
+ }
114295
114810
  if (pending2) return { kind: "decision-pending", taskId: signal.activeTaskId };
114296
114811
  let taskId = signal.activeTaskId;
114297
114812
  if (taskId === null)
@@ -114300,28 +114815,28 @@ async function attemptActiveRunSelfHeal2(signal, runs, deps2) {
114300
114815
  taskId: null,
114301
114816
  detail: "the engine did not name the run that holds this session"
114302
114817
  };
114303
- let planVerdict = async () => {
114818
+ let planVerdict2 = async () => {
114304
114819
  try {
114305
114820
  return await deps2?.reopenPlanReview?.(taskId) ?? { reopened: !1 };
114306
114821
  } catch {
114307
114822
  return { reopened: !1 };
114308
114823
  }
114309
- }, askVerdict = async () => {
114824
+ }, askVerdict2 = async () => {
114310
114825
  try {
114311
114826
  return await deps2?.reopenAskPark?.(taskId) ?? { reopened: !1 };
114312
114827
  } catch {
114313
114828
  return { reopened: !1 };
114314
114829
  }
114315
114830
  }, reopenPlanArm = async () => {
114316
- let verdict = await planVerdict();
114831
+ let verdict = await planVerdict2();
114317
114832
  return verdict.reopened === !0 ? { kind: "plan-review-reopened", taskId, firstSight: verdict.firstSight === !0 } : { kind: "plan-review-reopen-failed", taskId, decidePath: signal.pendingGate?.decidePath ?? null };
114318
114833
  }, reopenAskArm = async () => {
114319
- let verdict = await askVerdict();
114834
+ let verdict = await askVerdict2();
114320
114835
  return verdict.reopened === !0 ? { kind: "ask-reopened", taskId, firstSight: verdict.firstSight === !0 } : { kind: "ask-reopen-failed", taskId, decidePath: signal.pendingGate?.decidePath ?? null };
114321
- }, runningChoiceArm = async (status4) => {
114836
+ }, runningChoiceArm2 = async (status4) => {
114322
114837
  let notParked = { kind: "not-parked", taskId, status: status4 }, offer = deps2?.offerRunningChoice;
114323
114838
  if (typeof offer != "function") return notParked;
114324
- let text2 = typeof deps2?.deniedMessage == "string" ? deps2.deniedMessage : "", steer = runs?.steer, cancel = runs?.cancel, get4 = runs?.get, canSteer = typeof steer == "function" && text2.length > 0, canCancel = typeof cancel == "function" && typeof get4 == "function";
114839
+ let text2 = typeof deps2?.deniedMessage == "string" ? deps2.deniedMessage : "", steer = typeof runs?.steer == "function" ? runs.steer.bind(runs) : void 0, cancel = typeof runs?.cancel == "function" ? runs.cancel.bind(runs) : void 0, get4 = typeof runs?.get == "function" ? runs.get.bind(runs) : void 0, canSteer = typeof steer == "function" && text2.length > 0, canCancel = typeof cancel == "function" && typeof get4 == "function";
114325
114840
  if (!canSteer && !canCancel) return notParked;
114326
114841
  let choice = "wait";
114327
114842
  try {
@@ -114333,7 +114848,7 @@ async function attemptActiveRunSelfHeal2(signal, runs, deps2) {
114333
114848
  try {
114334
114849
  let receipt = await steer(taskId, { text: text2 }), delivery = readDelivery(receipt), receiptStatus = readReceiptStatus(receipt);
114335
114850
  if (delivery === "queued") {
114336
- let verdict = receiptStatus !== null && PLAN_REVIEW_STATES2.includes(receiptStatus) ? await planVerdict() : receiptStatus !== null && ASK_PARK_STATES2.includes(receiptStatus) ? await askVerdict() : null;
114851
+ let verdict = receiptStatus !== null && PLAN_REVIEW_STATES2.includes(receiptStatus) ? await planVerdict2() : receiptStatus !== null && ASK_PARK_STATES2.includes(receiptStatus) ? await askVerdict2() : null;
114337
114852
  return { kind: "running-steered", taskId, delivery, status: receiptStatus, reopened: verdict };
114338
114853
  }
114339
114854
  return { kind: "running-steered", taskId, delivery, status: receiptStatus, reopened: null };
@@ -114346,12 +114861,12 @@ async function attemptActiveRunSelfHeal2(signal, runs, deps2) {
114346
114861
  } catch (e) {
114347
114862
  return { kind: "running-cancel-failed", taskId, detail: describeFailure2(e) };
114348
114863
  }
114349
- let verdict = await waitForClaimRelease(taskId, get4, cancelReleaseWaitMs(), deps2?.signal);
114864
+ let verdict = await waitForClaimRelease2(taskId, get4, cancelReleaseWaitMs(), deps2?.signal);
114350
114865
  return verdict.released ? { kind: "running-cancelled", taskId } : { kind: "running-cancel-timeout", taskId, waitedMs: verdict.waitedMs, aborted: verdict.aborted };
114351
114866
  }
114352
114867
  return notParked;
114353
114868
  }, gateKind2 = signal.pendingGate?.kind ?? null;
114354
- if (gateKind2 !== null && PLAN_REVIEW_GATE_KINDS.includes(gateKind2)) return reopenPlanArm();
114869
+ if (gateKind2 !== null && PLAN_REVIEW_GATE_KINDS2.includes(gateKind2)) return reopenPlanArm();
114355
114870
  if (gateKind2 !== null && ASK_PARK_GATE_KINDS2.includes(gateKind2)) return reopenAskArm();
114356
114871
  let status3 = signal.activeTaskStatus;
114357
114872
  if (status3 === null) {
@@ -114365,7 +114880,7 @@ async function attemptActiveRunSelfHeal2(signal, runs, deps2) {
114365
114880
  }
114366
114881
  status3 = readStatus2(record2);
114367
114882
  }
114368
- return status3 === null ? { kind: "state-unknown", taskId, detail: "the engine reported no status for that run" } : gateKind2 !== null ? { kind: "not-parked", taskId, status: status3 } : PLAN_REVIEW_STATES2.includes(status3) ? reopenPlanArm() : ASK_PARK_STATES2.includes(status3) ? reopenAskArm() : RUNNING_STATES.includes(status3) ? runningChoiceArm(status3) : { kind: "not-parked", taskId, status: status3 };
114883
+ return status3 === null ? { kind: "state-unknown", taskId, detail: "the engine reported no status for that run" } : gateKind2 !== null ? { kind: "not-parked", taskId, status: status3 } : PLAN_REVIEW_STATES2.includes(status3) ? reopenPlanArm() : ASK_PARK_STATES2.includes(status3) ? reopenAskArm() : RUNNING_STATES2.includes(status3) ? runningChoiceArm2(status3) : { kind: "not-parked", taskId, status: status3 };
114369
114884
  }
114370
114885
  function gateKindPhrase2(kind) {
114371
114886
  return kind ? `a ${kind} decision` : "a decision";
@@ -114439,11 +114954,11 @@ function activeRunBusyHeadlessRow2(signal) {
114439
114954
  // 用户唯一能读到的就是这一段话。
114440
114955
  governanceOriginClause2(signal) : status3 ? `This session is held by an earlier run${handle2} with status ${status3}, so this message was NOT sent. The engine reported no decision pending on that run: if it is still working, wait for it to finish; if it will never settle, release it with ${cancelPath} (that discards whatever it was doing). Or ${freshSession}.` : `This session is locked by an earlier run${handle2} that was never released, so this message was NOT sent. Nothing here clears on its own. Start a new session (drop --resume/--continue), or release the run on the engine: ${cancelPath}`;
114441
114956
  }
114442
- var PLAN_REVIEW_STATES2, ASK_PARK_STATES2, PLAN_REVIEW_GATE_KINDS, ASK_PARK_GATE_KINDS2, RUNNING_STATES, CLAIM_RELEASED_STATES, CANCEL_RELEASE_WAIT_MS, CANCEL_POLL_START_MS, CANCEL_POLL_MAX_MS, cancelReleaseWaitOverrideMs, INTERACTIVE_WAY_OUT, init_activeRunSelfHeal2 = __esm({
114957
+ var PLAN_REVIEW_STATES2, ASK_PARK_STATES2, PLAN_REVIEW_GATE_KINDS2, ASK_PARK_GATE_KINDS2, RUNNING_STATES2, CLAIM_RELEASED_STATES2, CANCEL_RELEASE_WAIT_MS2, CANCEL_POLL_START_MS2, CANCEL_POLL_MAX_MS2, cancelReleaseWaitOverrideMs, INTERACTIVE_WAY_OUT, init_activeRunSelfHeal2 = __esm({
114443
114958
  "build-src/src/sema/activeRunSelfHeal.ts"() {
114444
114959
  init_dist();
114445
- PLAN_REVIEW_STATES2 = ["needs_review"], ASK_PARK_STATES2 = ["suspended"], PLAN_REVIEW_GATE_KINDS = ["plan_review", "dry_run_review"], ASK_PARK_GATE_KINDS2 = ["human", "irreversible_ask", "policy_ask", "tool_approval"], RUNNING_STATES = ["running"], CLAIM_RELEASED_STATES = ["completed", "failed", "blocked", "timeout"];
114446
- CANCEL_RELEASE_WAIT_MS = 1e4, CANCEL_POLL_START_MS = 200, CANCEL_POLL_MAX_MS = 2e3, cancelReleaseWaitOverrideMs = null;
114960
+ PLAN_REVIEW_STATES2 = ["needs_review"], ASK_PARK_STATES2 = ["suspended"], PLAN_REVIEW_GATE_KINDS2 = ["plan_review", "dry_run_review"], ASK_PARK_GATE_KINDS2 = ["human", "irreversible_ask", "policy_ask", "tool_approval"], RUNNING_STATES2 = ["running"], CLAIM_RELEASED_STATES2 = ["completed", "failed", "blocked", "timeout"];
114961
+ CANCEL_RELEASE_WAIT_MS2 = 1e4, CANCEL_POLL_START_MS2 = 200, CANCEL_POLL_MAX_MS2 = 2e3, cancelReleaseWaitOverrideMs = null;
114447
114962
  INTERACTIVE_WAY_OUT = "run /clear to keep working in a fresh session";
114448
114963
  }
114449
114964
  });
@@ -178379,7 +178894,8 @@ function _resetPlanReviewReopenSeqForTest() {
178379
178894
  for (let [, entry] of activeReopenByCanonical)
178380
178895
  try {
178381
178896
  entry.unregister();
178382
- } catch {
178897
+ } catch (e) {
178898
+ failOpen("plan-review-reopen-unregister", void 0, String(e));
178383
178899
  }
178384
178900
  activeReopenByCanonical.clear();
178385
178901
  }
@@ -178435,6 +178951,7 @@ var APPROVE_LABEL2, REJECT_LABEL2, reopenSeq, activeReopenByCanonical, init_plan
178435
178951
  init_dist();
178436
178952
  init_armedGateRegistry2();
178437
178953
  init_transcriptSystemNotice();
178954
+ init_failOpen();
178438
178955
  APPROVE_LABEL2 = PLAN_REVIEW_APPROVE_LABEL, REJECT_LABEL2 = PLAN_REVIEW_REJECT_LABEL, reopenSeq = 0, activeReopenByCanonical = /* @__PURE__ */ new Map();
178439
178956
  }
178440
178957
  });
@@ -178483,95 +179000,19 @@ var DECIDE_RETRY_DELAY_MS, delayOverrideMs, init_decideRetry = __esm({
178483
179000
  }
178484
179001
  });
178485
179002
 
178486
- // build-src/src/sema/parkRowBirthWait.ts
178487
- function abortableDelay(ms, signal) {
178488
- return signal?.aborted === !0 ? Promise.resolve() : new Promise((resolve57) => {
178489
- let done = () => {
178490
- clearTimeout(timer2);
178491
- try {
178492
- signal?.removeEventListener("abort", done);
178493
- } catch {
178494
- }
178495
- resolve57();
178496
- }, timer2 = setTimeout(done, ms);
178497
- try {
178498
- signal?.addEventListener("abort", done, { once: !0 });
178499
- } catch {
178500
- }
178501
- });
178502
- }
178503
- async function waitForParkRowBirth(deps2) {
178504
- let now2 = typeof deps2.now == "function" ? deps2.now : () => Date.now(), sleep10 = typeof deps2.sleep == "function" ? deps2.sleep : abortableDelay, budgetMs = Number.isFinite(deps2.budgetMs) && deps2.budgetMs > 0 ? deps2.budgetMs : 0, intervalMs = Number.isFinite(deps2.intervalMs) && deps2.intervalMs > 0 ? deps2.intervalMs : 1, startedAt = now2(), probes = 0, lastReason = "no decidable pending row for this park", aborted2 = () => deps2.signal?.aborted === !0, safeProbe = async (attempt, signal) => {
178505
- try {
178506
- return await deps2.probe(attempt, signal);
178507
- } catch (e) {
178508
- return { kind: "unborn", reason: `probe threw: ${String(e)}` };
178509
- }
178510
- }, probeWithinRemaining = async (attempt, remainingMs) => {
178511
- if (budgetMs <= 0) return safeProbe(attempt, deps2.signal);
178512
- let ctl = new AbortController(), timer2, deadline = new Promise((resolve57) => {
178513
- timer2 = setTimeout(() => {
178514
- ctl.abort(), resolve57("deadline");
178515
- }, Math.max(1, remainingMs));
178516
- }), caller = deps2.signal, onAbort, externalAbort = new Promise((resolve57) => {
178517
- if (caller !== void 0) {
178518
- if (caller.aborted) {
178519
- resolve57("aborted");
178520
- return;
178521
- }
178522
- onAbort = () => resolve57("aborted");
178523
- try {
178524
- caller.addEventListener("abort", onAbort, { once: !0 });
178525
- } catch {
178526
- onAbort = void 0;
178527
- }
178528
- }
178529
- }), merged = caller !== void 0 && typeof AbortSignal.any == "function" ? AbortSignal.any([caller, ctl.signal]) : ctl.signal;
178530
- try {
178531
- return await Promise.race([safeProbe(attempt, merged), deadline, externalAbort]);
178532
- } finally {
178533
- if (timer2 !== void 0 && clearTimeout(timer2), onAbort !== void 0 && caller !== void 0)
178534
- try {
178535
- caller.removeEventListener("abort", onAbort);
178536
- } catch {
178537
- }
178538
- }
178539
- };
178540
- for (; ; ) {
178541
- if (aborted2()) return { kind: "aborted", waitedMs: now2() - startedAt, probes };
178542
- probes += 1;
178543
- let outcome = await probeWithinRemaining(probes, budgetMs - (now2() - startedAt));
178544
- if (outcome === "aborted" || aborted2()) return { kind: "aborted", waitedMs: now2() - startedAt, probes };
178545
- if (outcome === "deadline")
178546
- return {
178547
- kind: "unborn",
178548
- reason: `${lastReason} (the last read face did not answer within the remaining window)`,
178549
- waitedMs: now2() - startedAt,
178550
- probes
178551
- };
178552
- let probed = outcome;
178553
- if (probed.kind === "row") return { kind: "row", row: probed.row, waitedMs: now2() - startedAt, probes };
178554
- if (probed.kind === "settled")
178555
- return { kind: "settled", reason: probed.reason, waitedMs: now2() - startedAt, probes };
178556
- lastReason = probed.reason;
178557
- let remainingMs = budgetMs - (now2() - startedAt);
178558
- if (remainingMs <= 0) return { kind: "unborn", reason: lastReason, waitedMs: now2() - startedAt, probes };
178559
- if (await sleep10(Math.min(intervalMs, remainingMs), deps2.signal), aborted2()) return { kind: "aborted", waitedMs: now2() - startedAt, probes };
178560
- }
178561
- }
178562
- var init_parkRowBirthWait = __esm({
178563
- "build-src/src/sema/parkRowBirthWait.ts"() {
178564
- }
178565
- });
178566
-
178567
179003
  // build-src/src/sema/askParkReopen.ts
178568
179004
  var askParkReopen_exports = {};
178569
179005
  __export(askParkReopen_exports, {
178570
179006
  _resetAskParkReopenInFlightForTest: () => _resetAskParkReopenInFlightForTest,
179007
+ _rowArmFlightForTest: () => _rowArmFlightForTest,
178571
179008
  _setAskParkRowWaitForTest: () => _setAskParkRowWaitForTest,
178572
179009
  askParkRowWaitMs: () => askParkRowWaitMs,
179010
+ countOwnedPendingApprovals: () => countOwnedPendingApprovals,
178573
179011
  reopenAskParkCard: () => reopenAskParkCard
178574
179012
  });
179013
+ async function countOwnedPendingApprovals(client3) {
179014
+ return ((await client3.approvals.list()).pending ?? []).filter((row2) => pendingRowIsOwnedByThisSession(row2, ownershipDeps)).length;
179015
+ }
178575
179016
  function reopenAskParkCard(taskId, client3, signal) {
178576
179017
  if (typeof taskId != "string" || taskId.length === 0) return Promise.resolve({ reopened: !1 });
178577
179018
  let inFlight4 = inFlightReopenByTask.get(taskId);
@@ -178586,7 +179027,10 @@ function reopenAskParkCard(taskId, client3, signal) {
178586
179027
  ), run2;
178587
179028
  }
178588
179029
  function _resetAskParkReopenInFlightForTest() {
178589
- inFlightReopenByTask.clear(), inFlightArmByRow.clear();
179030
+ inFlightReopenByTask.clear(), rowArmSingleFlight().clear();
179031
+ }
179032
+ function _rowArmFlightForTest() {
179033
+ return rowArmSingleFlight();
178590
179034
  }
178591
179035
  function _setAskParkRowWaitForTest(o) {
178592
179036
  o.budgetMs !== void 0 && (rowWaitOverrideMs = o.budgetMs), o.intervalMs !== void 0 && (rowPollOverrideMs = o.intervalMs);
@@ -178604,29 +179048,19 @@ async function probeOwnedAskRow(taskId, client3, signal) {
178604
179048
  } catch (e) {
178605
179049
  return { kind: "unborn", reason: `approvals.list failed for task ${taskId}: ${String(e)}` };
178606
179050
  }
178607
- let own2 = rows2.find((r) => r.taskId === taskId), sole = rows2.length === 1 ? rows2[0] : void 0, soleUnowned = sole !== void 0 && !pendingRowIsOwnedByThisSession(sole, ownershipDeps), pending2 = own2 ?? (soleUnowned ? void 0 : sole);
178608
- if (!pending2)
178609
- return {
178610
- kind: "unborn",
178611
- reason: rows2.length === 0 ? (
178612
- // 空表两义(#269):要么真孤儿 park(座位占着却没有待决行 = 引擎态异常),要么下一只门的
178613
- // 行还没铸出来。两义分不开 ⇒ 按可修的那一义有界重查,窗尽才按前者如实收口。
178614
- `no pending row at all for task ${taskId} \u2014 either an orphan park (a seat held with no decision outstanding) or the next gate's row is not minted yet`
178615
- ) : soleUnowned ? `the only pending row (task ${String(sole?.taskId)} / session ${String(sole?.sessionId)}) could not be proven to belong to this shell (not in its own-run ledger, session id absent or different) \u2014 refusing to surface an approval that may be another session's` : `${String(rows2.length)} pending rows but none for task ${taskId} \u2014 ambiguous queue, refusing to surface an unrelated row`
178616
- };
178617
- let rowGateKind = typeof pending2.gateKind == "string" && pending2.gateKind.length > 0 ? pending2.gateKind : null;
178618
- return rowGateKind !== null && !ASK_PARK_GATE_KINDS2.includes(rowGateKind) ? {
178619
- kind: "unborn",
178620
- reason: `the visible pending row (task ${String(pending2.taskId)}) is parked on a '${rowGateKind}' gate \u2014 not an ask/approval gate; this arm will not surface or decide it, and it may also be the previous gate's row still visible during a transition`
178621
- } : { kind: "row", row: pending2 };
179051
+ return classifyAskParkRows(rows2, taskId, { ownership: ownershipDeps });
178622
179052
  }
178623
179053
  async function reopenAskParkCardOnce(taskId, client3, signal) {
178624
179054
  try {
178625
179055
  if (typeof taskId != "string" || taskId.length === 0) return { reopened: !1 };
178626
- let deadlineAt = Date.now() + askParkRowWaitMs();
179056
+ let deadlineAt = Date.now() + askParkRowWaitMs(), flight = rowArmSingleFlight();
178627
179057
  for (; ; ) {
178628
179058
  let found = await waitForParkRowBirth({
178629
- probe: () => probeOwnedAskRow(taskId, client3, signal),
179059
+ // 🔴 第二参 = 重查环给的**本拍**合流信号(外部 Esc × 剩余预算),必须原样透传进真实读面
179060
+ // ——包的 `ParkRowBirthWaitDeps.probe` 头注把这条写成红线。丢掉它(改回零参、或顶替成
179061
+ // 外层 caller signal)⇒「有界」只在判决面成立:窗尽时环如实收口,而那条挂死的
179062
+ // `approvals.list` 没人去掐,连接活到进程结束。常驻反钉 = activeRunSelfHeal 套 N-o1。
179063
+ probe: (_attempt, probeSignal) => probeOwnedAskRow(taskId, client3, probeSignal ?? signal),
178630
179064
  budgetMs: Math.max(0, deadlineAt - Date.now()),
178631
179065
  intervalMs: askParkRowPollMs(),
178632
179066
  ...signal ? { signal } : {}
@@ -178635,7 +179069,7 @@ async function reopenAskParkCardOnce(taskId, client3, signal) {
178635
179069
  return process.env.SEMA_DEBUG && console.error(
178636
179070
  `[sema][askParkReopen] no decidable pending row for task ${taskId} after ${String(found.waitedMs)}ms / ${String(found.probes)} probe(s) \u2014 ${found.kind === "aborted" ? "the wait was interrupted" : found.reason} \u2014 reporting reopened:false`
178637
179071
  ), { reopened: !1 };
178638
- let armed3 = await armPendingRowCoalesced(taskId, found.row, client3);
179072
+ let armed3 = await armPendingRowCoalesced(flight, taskId, found.row, client3);
178639
179073
  if (armed3.kind === "verdict") return armed3.verdict;
178640
179074
  if (Date.now() >= deadlineAt || signal?.aborted === !0)
178641
179075
  return process.env.SEMA_DEBUG && console.error(
@@ -178649,36 +179083,32 @@ async function reopenAskParkCardOnce(taskId, client3, signal) {
178649
179083
  return process.env.SEMA_DEBUG && console.error(`[sema][askParkReopen] reopen lane for task ${taskId} threw (${String(e)}) \u2014 reporting reopened:false`), { reopened: !1 };
178650
179084
  }
178651
179085
  }
178652
- function rowIdentityOf(taskId, pending2) {
178653
- let rowTaskId = typeof pending2.taskId == "string" && pending2.taskId.length > 0 ? pending2.taskId : taskId, gatedCallId = pending2.toolCallId ?? pending2.boundCallId ?? void 0;
178654
- return { rowTaskId, gatedCallId, armedKey: approvalCallKey(gatedCallId, rowTaskId) };
179086
+ function rowArmSingleFlight() {
179087
+ let epoch = getLiveSessionEpoch();
179088
+ return (rowArmFlight === null || epoch !== rowArmFlightEpoch) && (rowArmFlightEpoch = epoch, rowArmFlight = createRowArmSingleFlight()), rowArmFlight;
178655
179089
  }
178656
- function armPendingRowCoalesced(taskId, pending2, client3) {
178657
- let identity3 = rowIdentityOf(taskId, pending2), inFlight4 = inFlightArmByRow.get(identity3.armedKey);
178658
- if (inFlight4 !== void 0)
178659
- return process.env.SEMA_DEBUG && console.error(
178660
- `[sema][askParkReopen] a card for row ${identity3.armedKey} is already being presented \u2014 joining it instead of minting a second card (requested by task ${taskId})`
178661
- ), inFlight4;
178662
- let run2 = armPendingRow(taskId, pending2, client3, identity3);
178663
- return inFlightArmByRow.set(identity3.armedKey, run2), run2.then(
178664
- () => inFlightArmByRow.delete(identity3.armedKey),
178665
- () => inFlightArmByRow.delete(identity3.armedKey)
178666
- ), run2;
179090
+ function armPendingRowCoalesced(flight, taskId, pending2, client3) {
179091
+ let identity3 = askParkRowIdentity(taskId, pending2);
179092
+ return flight.size() > 0 && process.env.SEMA_DEBUG && console.error(
179093
+ `[sema][askParkReopen] row-arm single flight holds ${String(flight.size())} in-flight chain(s); a card for row ${identity3.armedKey} that is already being presented will be joined instead of minting a second one (requested by task ${taskId})`
179094
+ ), flight.join(identity3.armedKey, () => armPendingRow(taskId, pending2, client3, identity3));
178667
179095
  }
178668
179096
  async function armPendingRow(taskId, pending2, client3, identity3) {
178669
179097
  try {
178670
179098
  let { rowTaskId, gatedCallId, armedKey } = identity3, firstSight = !wasGateArmed2(armedKey), rowStillPending = async () => {
178671
179099
  try {
178672
- return (await client3.approvals.list()).pending.some(
178673
- (r) => gatedCallId !== void 0 ? (r.toolCallId ?? r.boundCallId ?? void 0) === gatedCallId : r.taskId === rowTaskId
178674
- );
179100
+ return askParkRowStillPending((await client3.approvals.list()).pending, identity3);
178675
179101
  } catch {
178676
179102
  return !0;
178677
179103
  }
178678
- }, rowGateKind = typeof pending2.gateKind == "string" && pending2.gateKind.length > 0 ? pending2.gateKind : null, askQuestions = askQuestionsFromPending(pending2), toolNameIsAsk = isAskTool(pending2.toolName ?? void 0), rowHasToolName = typeof pending2.toolName == "string" && pending2.toolName.trim().length > 0, questionGate = rowGateKind === null ? toolNameIsAsk : rowGateKind === "human" ? toolNameIsAsk || askQuestions !== null || !rowHasToolName : !1;
178679
- if (process.env.SEMA_DEBUG && console.error(
178680
- `[sema][askParkReopen] routing row (task ${rowTaskId} \xB7 gateKind ${rowGateKind ?? "null"} \xB7 toolName ${String(pending2.toolName ?? "null")} \xB7 callId ${String(gatedCallId ?? "null")}) \u21D2 ${questionGate ? "question" : "tool-gate"} arm`
178681
- ), questionGate) {
179104
+ }, askQuestions = askQuestionsFromPending(pending2), questionGate = askParkRowArm(pending2) === "question";
179105
+ if (process.env.SEMA_DEBUG) {
179106
+ let rowGateKind = typeof pending2.gateKind == "string" && pending2.gateKind.length > 0 ? pending2.gateKind : null;
179107
+ console.error(
179108
+ `[sema][askParkReopen] routing row (task ${rowTaskId} \xB7 gateKind ${rowGateKind ?? "null"} \xB7 toolName ${String(pending2.toolName ?? "null")} \xB7 callId ${String(gatedCallId ?? "null")}) \u21D2 ${questionGate ? "question" : "tool-gate"} arm`
179109
+ );
179110
+ }
179111
+ if (questionGate) {
178682
179112
  if (!hasQuestionOverlay())
178683
179113
  return process.env.SEMA_DEBUG && console.error(
178684
179114
  `[sema][askParkReopen] question arm for task ${rowTaskId} has no overlay subscriber (headless / REPL not mounted) \u2014 reporting reopened:false`
@@ -178693,7 +179123,8 @@ async function armPendingRow(taskId, pending2, client3, identity3) {
178693
179123
  let questionId = `ask-park:${rowTaskId}${REOPEN_ID_TAIL}${String(reopenSeq2)}`, unregister = registerLocalQuestionResponder(questionId, async (_id, answer) => {
178694
179124
  try {
178695
179125
  unregister();
178696
- } catch {
179126
+ } catch (e) {
179127
+ failOpen("ask-park-reopen-unregister", void 0, String(e));
178697
179128
  }
178698
179129
  let deliver = async () => {
178699
179130
  try {
@@ -178730,7 +179161,13 @@ async function armPendingRow(taskId, pending2, client3, identity3) {
178730
179161
  ), { kind: "verdict", verdict: { reopened: !1 } };
178731
179162
  let cardPresented = !1, unsubscribeArm = onGateArmed((key) => {
178732
179163
  key === armedKey && (cardPresented = !0);
178733
- }), chainNote = { vanishedBeforeCard: null }, classifyChainFailure = async (detail) => await rowStillPending() ? { ok: !1, detail } : cardPresented ? { ok: !0 } : (chainNote.vanishedBeforeCard = detail, { ok: !0 }), runToolChain = async () => {
179164
+ }), chainNote = { vanishedBeforeCard: null }, classifyChainFailure = async (detail) => {
179165
+ let disposition = classifyAskParkChainFailure({
179166
+ rowStillPending: await rowStillPending(),
179167
+ cardPresented
179168
+ });
179169
+ return disposition === "retry" ? { ok: !1, detail } : (disposition === "row-unborn" && (chainNote.vanishedBeforeCard = detail), { ok: !0 });
179170
+ }, runToolChain = async () => {
178734
179171
  try {
178735
179172
  let out6 = await surfaceFsApprovalAndDecide({ client: client3 }, rowTaskId, /* @__PURE__ */ new Map());
178736
179173
  return out6.kind !== "failed" ? { ok: !0 } : classifyChainFailure(out6.reason);
@@ -178764,17 +179201,198 @@ async function armPendingRow(taskId, pending2, client3, identity3) {
178764
179201
  ), { kind: "verdict", verdict: { reopened: !1 } };
178765
179202
  }
178766
179203
  }
178767
- var reopenSeq2, ownershipDeps, inFlightReopenByTask, ASK_PARK_ROW_WAIT_MS, ASK_PARK_ROW_POLL_MS, rowWaitOverrideMs, rowPollOverrideMs, inFlightArmByRow, init_askParkReopen = __esm({
179204
+ var reopenSeq2, ownershipDeps, inFlightReopenByTask, rowWaitOverrideMs, rowPollOverrideMs, rowArmFlightEpoch, rowArmFlight, init_askParkReopen = __esm({
178768
179205
  "build-src/src/sema/askParkReopen.ts"() {
178769
179206
  init_dist();
178770
179207
  init_liveSessionStore();
178771
- init_activeRunSelfHeal2();
178772
179208
  init_armedGateRegistry2();
178773
179209
  init_decideRetry();
178774
- init_parkRowBirthWait();
178775
- reopenSeq2 = 0, ownershipDeps = { currentSessionId: getLiveSessionId }, inFlightReopenByTask = /* @__PURE__ */ new Map();
178776
- ASK_PARK_ROW_WAIT_MS = 15e3, ASK_PARK_ROW_POLL_MS = 500, rowWaitOverrideMs = null, rowPollOverrideMs = null;
178777
- inFlightArmByRow = /* @__PURE__ */ new Map();
179210
+ init_failOpen();
179211
+ reopenSeq2 = 0, ownershipDeps = {
179212
+ currentSessionId: getLiveSessionId,
179213
+ isOwnRun: (taskIdTail) => {
179214
+ if (isOwnEngineRun(taskIdTail)) return !0;
179215
+ let persisted = restoredEngineTaskId();
179216
+ return typeof persisted == "string" && persisted.length > 0 && taskIdTail === rowIdTail(persisted);
179217
+ }
179218
+ };
179219
+ inFlightReopenByTask = /* @__PURE__ */ new Map();
179220
+ rowWaitOverrideMs = null, rowPollOverrideMs = null;
179221
+ rowArmFlightEpoch = -1, rowArmFlight = null;
179222
+ }
179223
+ });
179224
+
179225
+ // build-src/src/sema/activeRunRunningChoice.ts
179226
+ function choiceAnswerWaitMs() {
179227
+ return answerWaitOverrideMs ?? CHOICE_ANSWER_WAIT_MS;
179228
+ }
179229
+ function openRunningChoiceCardCount() {
179230
+ return openCardCount;
179231
+ }
179232
+ async function offerActiveRunChoice(req) {
179233
+ try {
179234
+ let { taskId } = req;
179235
+ if (typeof taskId != "string" || taskId.length === 0 || !req.canSteer && !req.canCancel || !hasQuestionOverlay() || req.signal?.aborted === !0) return "wait";
179236
+ if (offeringByTask.has(taskId))
179237
+ return process.env.SEMA_DEBUG && console.error(
179238
+ `[sema][activeRunRunningChoice] a choice card for run ${taskId} is already open \u2014 this caller waits instead of minting a second card`
179239
+ ), "wait";
179240
+ offeringByTask.add(taskId);
179241
+ try {
179242
+ let options = [
179243
+ ...req.canSteer ? [
179244
+ {
179245
+ label: RUNNING_CHOICE_STEER_LABEL,
179246
+ // 🔴 两句都是**兑现得了**的话(1.0.75 车B):①「it picks it up at its next step」对一条
179247
+ // 已经结束/已经 park 的 run 不成立 —— 引擎的回执才知道真到了哪儿(delivery 三分形),
179248
+ // 所以这里只说「交给引擎」,不替引擎承诺投递时点;②steer 的 wire body 只有 `text`
179249
+ // (SDK runs.steer 签名无 images 位),同一次提交里的图片不随行,按下之前就得知道。
179250
+ // 🔴 不许把「一定说得清落点」写成承诺(二次评审 [medium]):回执有**读不出投递语义**
179251
+ // 与**送达未知**(5xx / 回执路上断连)两条真分支,而 steer 非幂等 —— 对一条可能已经
179252
+ // 注入的指令说「我会告诉你它到底去哪了」,下一行的「无法确认」就当场打脸。
179253
+ description: 'Hands the TEXT of your message to the engine for the run that is already working \u2014 attachments do not ride along, and sema reports back whatever the engine says about where it landed (which can be "unconfirmed"). Nothing is cancelled.'
179254
+ }
179255
+ ] : [],
179256
+ ...req.canCancel ? [
179257
+ {
179258
+ label: RUNNING_CHOICE_CANCEL_LABEL,
179259
+ description: "Stops that run (whatever it has been doing is discarded), waits for it to release the session, then sends your message as a new turn."
179260
+ }
179261
+ ] : [],
179262
+ {
179263
+ label: RUNNING_CHOICE_WAIT_LABEL,
179264
+ description: "Leaves the run alone. Your message is NOT sent \u2014 send it again whenever you want."
179265
+ }
179266
+ ], decided = await presentChoiceCard({
179267
+ taskId,
179268
+ header: RUNNING_CHOICE_HEADER,
179269
+ question: `An earlier turn is still running in this session (run ${taskId}, status ${req.status}), so your message was not sent \u2014 a session runs one turn at a time. What should sema do with it?`,
179270
+ options,
179271
+ decode: choiceFromAnswer,
179272
+ onNoAnswer: "wait",
179273
+ ...req.signal ? { signal: req.signal } : {}
179274
+ });
179275
+ return decided === NOT_PRESENTED ? "wait" : decided;
179276
+ } finally {
179277
+ offeringByTask.delete(taskId);
179278
+ }
179279
+ } catch {
179280
+ return "wait";
179281
+ }
179282
+ }
179283
+ async function offerResumeRunningChoice(req) {
179284
+ try {
179285
+ let { taskId } = req;
179286
+ if (typeof taskId != "string" || taskId.length === 0 || !req.canAttach && !req.canCancel || !hasQuestionOverlay() || req.signal?.aborted === !0) return "unavailable";
179287
+ if (offeringByTask.has(taskId))
179288
+ return process.env.SEMA_DEBUG && console.error(
179289
+ `[sema][activeRunRunningChoice] a choice card for run ${taskId} is already open \u2014 the resume entry stands down instead of minting a second card`
179290
+ ), "unavailable";
179291
+ offeringByTask.add(taskId);
179292
+ try {
179293
+ let decided = await presentChoiceCard({
179294
+ taskId,
179295
+ header: RESUME_CHOICE_HEADER,
179296
+ question: resumeRunningQuestionText(req),
179297
+ // 🔴 顺序 = 默认焦点(裁点③:③ 必须是 options[0])—— 判据单源在 resumeRunningOptions。
179298
+ options: resumeRunningOptions(req),
179299
+ decode: (answer) => resumeChoiceFromLabels(answer?.answers?.[0]?.selected),
179300
+ // Esc / 看门狗到点 ⇒ ③(零动作)。#155:破坏性动作只认显式选择。
179301
+ onNoAnswer: "background",
179302
+ ...req.signal ? { signal: req.signal } : {}
179303
+ });
179304
+ return decided === NOT_PRESENTED ? "unavailable" : decided;
179305
+ } finally {
179306
+ offeringByTask.delete(taskId);
179307
+ }
179308
+ } catch {
179309
+ return "unavailable";
179310
+ }
179311
+ }
179312
+ async function presentChoiceCard(spec) {
179313
+ let taskId = spec.taskId;
179314
+ cardSeq += 1;
179315
+ let questionId = `${QUESTION_ID_PREFIX}${taskId}${REOPEN_ID_TAIL}${String(cardSeq)}`, settle2 = () => {
179316
+ }, decided = new Promise((resolve57) => {
179317
+ settle2 = resolve57;
179318
+ }), unregister = registerLocalQuestionResponder(questionId, (_id, answer) => (settle2(spec.decode(answer)), Promise.resolve({ ok: !0 }))), retired = !1, counted = !1, retire = () => {
179319
+ if (!retired) {
179320
+ retired = !0, counted && (counted = !1, openCardCount = Math.max(0, openCardCount - 1));
179321
+ try {
179322
+ unregister();
179323
+ } catch {
179324
+ }
179325
+ publishQuestionFrame({ type: "question_complete", questionId });
179326
+ }
179327
+ }, frame = {
179328
+ type: "question",
179329
+ questionId,
179330
+ questions: [
179331
+ {
179332
+ header: spec.header,
179333
+ question: spec.question,
179334
+ options: spec.options,
179335
+ multiSelect: !1
179336
+ }
179337
+ ]
179338
+ }, receipt = waitForGateArmed([questionId], gateArmedWaitMs());
179339
+ if (publishQuestionFrame(frame), !await receipt)
179340
+ return process.env.SEMA_DEBUG && console.error(
179341
+ `[sema][activeRunRunningChoice] question frame ${questionId} published but no presentation receipt arrived within ${String(gateArmedWaitMs())}ms \u2014 treating it as no decision`
179342
+ ), retire(), NOT_PRESENTED;
179343
+ counted = !0, openCardCount += 1;
179344
+ let watchdog = watchdogToNoAnswer(spec.onNoAnswer), abortWatch = abortToNoAnswer(spec.signal, spec.onNoAnswer), choice;
179345
+ try {
179346
+ choice = await Promise.race([decided, abortWatch.promise, watchdog.promise]);
179347
+ } finally {
179348
+ watchdog.cancel(), abortWatch.cancel();
179349
+ }
179350
+ return retire(), choice;
179351
+ }
179352
+ function abortToNoAnswer(signal, onNoAnswer) {
179353
+ if (signal === void 0) return { promise: new Promise(() => {
179354
+ }), cancel: () => {
179355
+ } };
179356
+ if (signal.aborted) return { promise: Promise.resolve(onNoAnswer), cancel: () => {
179357
+ } };
179358
+ let onAbort = () => {
179359
+ };
179360
+ return {
179361
+ promise: new Promise((resolve57) => {
179362
+ onAbort = () => resolve57(onNoAnswer), signal.addEventListener("abort", onAbort, { once: !0 });
179363
+ }),
179364
+ cancel: () => {
179365
+ try {
179366
+ signal.removeEventListener("abort", onAbort);
179367
+ } catch {
179368
+ }
179369
+ }
179370
+ };
179371
+ }
179372
+ function watchdogToNoAnswer(onNoAnswer) {
179373
+ let timer2;
179374
+ return {
179375
+ promise: new Promise((resolve57) => {
179376
+ timer2 = setTimeout(() => resolve57(onNoAnswer), choiceAnswerWaitMs());
179377
+ }),
179378
+ cancel: () => {
179379
+ timer2 !== void 0 && clearTimeout(timer2);
179380
+ }
179381
+ };
179382
+ }
179383
+ function choiceFromAnswer(answer) {
179384
+ let selected = answer?.answers?.[0]?.selected;
179385
+ if (!Array.isArray(selected) || selected.length !== 1) return "wait";
179386
+ let label = selected[0];
179387
+ return label === RUNNING_CHOICE_STEER_LABEL ? "steer" : label === RUNNING_CHOICE_CANCEL_LABEL ? "cancel" : "wait";
179388
+ }
179389
+ var RUNNING_CHOICE_STEER_LABEL, RUNNING_CHOICE_CANCEL_LABEL, RUNNING_CHOICE_WAIT_LABEL, RUNNING_CHOICE_HEADER, QUESTION_ID_PREFIX, CHOICE_ANSWER_WAIT_MS, answerWaitOverrideMs, cardSeq, offeringByTask, openCardCount, NOT_PRESENTED, init_activeRunRunningChoice = __esm({
179390
+ "build-src/src/sema/activeRunRunningChoice.ts"() {
179391
+ init_dist();
179392
+ init_armedGateRegistry2();
179393
+ RUNNING_CHOICE_STEER_LABEL = "Queue it into the running turn", RUNNING_CHOICE_CANCEL_LABEL = "Cancel that run and send this message now", RUNNING_CHOICE_WAIT_LABEL = "Do nothing for now", RUNNING_CHOICE_HEADER = "Session busy", QUESTION_ID_PREFIX = "active-run:", CHOICE_ANSWER_WAIT_MS = 3e5, answerWaitOverrideMs = null;
179394
+ cardSeq = 0, offeringByTask = /* @__PURE__ */ new Set(), openCardCount = 0;
179395
+ NOT_PRESENTED = /* @__PURE__ */ Symbol("sema.choiceCardNotPresented");
178778
179396
  }
178779
179397
  });
178780
179398
 
@@ -179068,63 +179686,6 @@ var REATTACH_BACKOFF_START_MS, REATTACH_BACKOFF_CAP_MS, REATTACH_WINDOW_MS, PROD
179068
179686
  });
179069
179687
 
179070
179688
  // build-src/src/sema/resumeRunningArm.ts
179071
- function resumeRunningLivenessRow(msSinceLastActivity) {
179072
- let ms = msSinceLastActivity;
179073
- return typeof ms != "number" || !Number.isFinite(ms) || ms < 0 ? null : `The engine last recorded activity on it ${String(Math.round(ms / 1e3))}s ago.`;
179074
- }
179075
- function resumeRunningQuestionText(i) {
179076
- let head = `This conversation was resumed while an earlier turn is still executing on the engine (run ${i.taskId}, status ${i.status}). sema did not touch it. What should it do?`, liveness = resumeRunningLivenessRow(i.msSinceLastActivity);
179077
- return liveness === null ? head : `${head}
179078
- ${liveness}`;
179079
- }
179080
- function resumeRunningOptions(i) {
179081
- let options = [
179082
- {
179083
- // 🔴 默认焦点(裁点③)—— 三条路里唯一零副作用的一条,所以它是手滑回车的落点。
179084
- // 🔴 措辞(1.0.75 车B / cli-049):这一项曾写「stays visible in /tasks」——**假的**。`/tasks`
179085
- // 面板读的是纯进程内的 `AppState.tasks`,而这张卡的前提恰恰是「那条 run 活得比上一个壳
179086
- // 进程久」,新进程里没有任何腿会为它建行(③ 的处置本身也是显式零动作)。默认焦点上的一句
179087
- // 假 affordance 是最坏的一格:手滑回车的人拿到一条既看不见、也停不掉、还占着会话锁的 run。
179088
- // 换成两句**可证的**真话:本屏不会列出它;它还占着会话,所以下一条消息会再撞上它(那时
179089
- // C3 三选卡会带着 steer/cancel 两条真出路出现)。
179090
- label: RESUME_CHOICE_BACKGROUND_LABEL,
179091
- description: "Leaves the run alone. It keeps executing on the engine, but this screen will not show it and it will not be listed here \u2014 your next message runs into it again, and sema asks you then."
179092
- }
179093
- ];
179094
- return i.canAttach && options.push({
179095
- label: RESUME_CHOICE_ATTACH_LABEL,
179096
- // 🔴 第二句是**知情同意**那一半(二次评审 r1-[high] 处置,见 resumeAttachReplayDisclosure):
179097
- // resume 入口没有事件锚可用,durable 尾只能从这条 run 的第一帧读起,所以崩溃前已经落盘
179098
- // 并被 resume 还原到屏上的那一段会再出现一次。用户按下之前就得看见这句 —— 一个会做出
179099
- // 用户没预料到的事的选项,和一个按了没用的选项同样是假 affordance。
179100
- description: "Follows that run from here: sema replays it from the engine ledger and keeps streaming it into this conversation. Anything from that turn already shown above will appear a second time \u2014 sema has no resume marker for it."
179101
- }), i.canCancel && options.push({
179102
- label: RESUME_CHOICE_CANCEL_LABEL,
179103
- description: "Stops that run on the engine (whatever it has been doing is discarded) and frees this session for a new turn."
179104
- }), options;
179105
- }
179106
- function resumeChoiceFromLabels(selected) {
179107
- if (!Array.isArray(selected) || selected.length !== 1) return "background";
179108
- let label = selected[0];
179109
- return label === RESUME_CHOICE_ATTACH_LABEL ? "attach" : label === RESUME_CHOICE_CANCEL_LABEL ? "cancel" : "background";
179110
- }
179111
- function resumeRunningNoUiGuidance(i) {
179112
- let sid = typeof i.sessionId == "string" && i.sessionId.length > 0 ? i.sessionId : null, attach = sid === null ? "sema --resume (pick this session from the list)" : `sema --resume ${sid}`, T3 = RESUME_RUNNING_GUIDANCE_TAG;
179113
- return [
179114
- `${T3} run ${i.taskId} from an earlier turn is still executing on the engine, and this lane has no card to ask you on. sema did NOT cancel it and did NOT attach to it.`,
179115
- `${T3} attach=${attach}`,
179116
- `${T3} cancel=POST /v1/runs/${i.taskId}/cancel`,
179117
- // 🔴 同 cli-049:这条车道更没有 `/tasks` 可看(headless 连 REPL 都不挂载)。给的是引擎侧真读面。
179118
- `${T3} background=do nothing \u2014 run ${i.taskId} keeps executing on the engine; this client does not list it (read it with GET /v1/runs/${i.taskId})`
179119
- ].join(`
179120
- `);
179121
- }
179122
- function resumeAttachReplayDisclosure(taskId) {
179123
- return `sema: attaching to run ${taskId} \u2014 sema is replaying it from the engine's ledger, so anything from that turn already shown above will appear again below. There is no resume marker for a run that outlived its shell. The replayed turns are not billed into this session's cost again either \u2014 the engine's own record (GET /v1/runs/${taskId}) is the authority on what that run really spent.`;
179124
- }
179125
- function resumeRunningCancelUnconfirmedRow(taskId) {
179126
- return `sema: the cancel for run ${taskId} did not come back confirmed, so that run may still be executing. This screen does not list it \u2014 read its real state on the engine (GET /v1/runs/${taskId}) before assuming this session is free.`;
179127
- }
179128
179689
  function resumeRunningActionableVerbs(deps2) {
179129
179690
  return {
179130
179691
  canAttach: deps2.canAttach === !0 && typeof deps2.attach == "function",
@@ -179254,186 +179815,10 @@ async function* resumeAttachStream(taskId, deps2, opts) {
179254
179815
  deps: deps2
179255
179816
  });
179256
179817
  }
179257
- var RESUME_CHOICE_ATTACH_LABEL, RESUME_CHOICE_CANCEL_LABEL, RESUME_CHOICE_BACKGROUND_LABEL, RESUME_CHOICE_HEADER, RESUME_RUNNING_NO_UI_EXIT_CODE, RESUME_RUNNING_GUIDANCE_TAG, init_resumeRunningArm = __esm({
179818
+ var init_resumeRunningArm = __esm({
179258
179819
  "build-src/src/sema/resumeRunningArm.ts"() {
179259
- init_interactiveReattach();
179260
- RESUME_CHOICE_ATTACH_LABEL = "Attach and watch", RESUME_CHOICE_CANCEL_LABEL = "Cancel it", RESUME_CHOICE_BACKGROUND_LABEL = "Leave it running in the background", RESUME_CHOICE_HEADER = "Earlier run still in flight";
179261
- RESUME_RUNNING_NO_UI_EXIT_CODE = 75, RESUME_RUNNING_GUIDANCE_TAG = "sema: resume-running:";
179262
- }
179263
- });
179264
-
179265
- // build-src/src/sema/activeRunRunningChoice.ts
179266
- function choiceAnswerWaitMs() {
179267
- return answerWaitOverrideMs ?? CHOICE_ANSWER_WAIT_MS;
179268
- }
179269
- function openRunningChoiceCardCount() {
179270
- return openCardCount;
179271
- }
179272
- async function offerActiveRunChoice(req) {
179273
- try {
179274
- let { taskId } = req;
179275
- if (typeof taskId != "string" || taskId.length === 0 || !req.canSteer && !req.canCancel || !hasQuestionOverlay() || req.signal?.aborted === !0) return "wait";
179276
- if (offeringByTask.has(taskId))
179277
- return process.env.SEMA_DEBUG && console.error(
179278
- `[sema][activeRunRunningChoice] a choice card for run ${taskId} is already open \u2014 this caller waits instead of minting a second card`
179279
- ), "wait";
179280
- offeringByTask.add(taskId);
179281
- try {
179282
- let options = [
179283
- ...req.canSteer ? [
179284
- {
179285
- label: RUNNING_CHOICE_STEER_LABEL,
179286
- // 🔴 两句都是**兑现得了**的话(1.0.75 车B):①「it picks it up at its next step」对一条
179287
- // 已经结束/已经 park 的 run 不成立 —— 引擎的回执才知道真到了哪儿(delivery 三分形),
179288
- // 所以这里只说「交给引擎」,不替引擎承诺投递时点;②steer 的 wire body 只有 `text`
179289
- // (SDK runs.steer 签名无 images 位),同一次提交里的图片不随行,按下之前就得知道。
179290
- // 🔴 不许把「一定说得清落点」写成承诺(二次评审 [medium]):回执有**读不出投递语义**
179291
- // 与**送达未知**(5xx / 回执路上断连)两条真分支,而 steer 非幂等 —— 对一条可能已经
179292
- // 注入的指令说「我会告诉你它到底去哪了」,下一行的「无法确认」就当场打脸。
179293
- description: 'Hands the TEXT of your message to the engine for the run that is already working \u2014 attachments do not ride along, and sema reports back whatever the engine says about where it landed (which can be "unconfirmed"). Nothing is cancelled.'
179294
- }
179295
- ] : [],
179296
- ...req.canCancel ? [
179297
- {
179298
- label: RUNNING_CHOICE_CANCEL_LABEL,
179299
- description: "Stops that run (whatever it has been doing is discarded), waits for it to release the session, then sends your message as a new turn."
179300
- }
179301
- ] : [],
179302
- {
179303
- label: RUNNING_CHOICE_WAIT_LABEL,
179304
- description: "Leaves the run alone. Your message is NOT sent \u2014 send it again whenever you want."
179305
- }
179306
- ], decided = await presentChoiceCard({
179307
- taskId,
179308
- header: RUNNING_CHOICE_HEADER,
179309
- question: `An earlier turn is still running in this session (run ${taskId}, status ${req.status}), so your message was not sent \u2014 a session runs one turn at a time. What should sema do with it?`,
179310
- options,
179311
- decode: choiceFromAnswer,
179312
- onNoAnswer: "wait",
179313
- ...req.signal ? { signal: req.signal } : {}
179314
- });
179315
- return decided === NOT_PRESENTED ? "wait" : decided;
179316
- } finally {
179317
- offeringByTask.delete(taskId);
179318
- }
179319
- } catch {
179320
- return "wait";
179321
- }
179322
- }
179323
- async function offerResumeRunningChoice(req) {
179324
- try {
179325
- let { taskId } = req;
179326
- if (typeof taskId != "string" || taskId.length === 0 || !req.canAttach && !req.canCancel || !hasQuestionOverlay() || req.signal?.aborted === !0) return "unavailable";
179327
- if (offeringByTask.has(taskId))
179328
- return process.env.SEMA_DEBUG && console.error(
179329
- `[sema][activeRunRunningChoice] a choice card for run ${taskId} is already open \u2014 the resume entry stands down instead of minting a second card`
179330
- ), "unavailable";
179331
- offeringByTask.add(taskId);
179332
- try {
179333
- let decided = await presentChoiceCard({
179334
- taskId,
179335
- header: RESUME_CHOICE_HEADER,
179336
- question: resumeRunningQuestionText(req),
179337
- // 🔴 顺序 = 默认焦点(裁点③:③ 必须是 options[0])—— 判据单源在 resumeRunningOptions。
179338
- options: resumeRunningOptions(req),
179339
- decode: (answer) => resumeChoiceFromLabels(answer?.answers?.[0]?.selected),
179340
- // Esc / 看门狗到点 ⇒ ③(零动作)。#155:破坏性动作只认显式选择。
179341
- onNoAnswer: "background",
179342
- ...req.signal ? { signal: req.signal } : {}
179343
- });
179344
- return decided === NOT_PRESENTED ? "unavailable" : decided;
179345
- } finally {
179346
- offeringByTask.delete(taskId);
179347
- }
179348
- } catch {
179349
- return "unavailable";
179350
- }
179351
- }
179352
- async function presentChoiceCard(spec) {
179353
- let taskId = spec.taskId;
179354
- cardSeq += 1;
179355
- let questionId = `${QUESTION_ID_PREFIX}${taskId}${REOPEN_ID_TAIL}${String(cardSeq)}`, settle2 = () => {
179356
- }, decided = new Promise((resolve57) => {
179357
- settle2 = resolve57;
179358
- }), unregister = registerLocalQuestionResponder(questionId, (_id, answer) => (settle2(spec.decode(answer)), Promise.resolve({ ok: !0 }))), retired = !1, counted = !1, retire = () => {
179359
- if (!retired) {
179360
- retired = !0, counted && (counted = !1, openCardCount = Math.max(0, openCardCount - 1));
179361
- try {
179362
- unregister();
179363
- } catch {
179364
- }
179365
- publishQuestionFrame({ type: "question_complete", questionId });
179366
- }
179367
- }, frame = {
179368
- type: "question",
179369
- questionId,
179370
- questions: [
179371
- {
179372
- header: spec.header,
179373
- question: spec.question,
179374
- options: spec.options,
179375
- multiSelect: !1
179376
- }
179377
- ]
179378
- }, receipt = waitForGateArmed([questionId], gateArmedWaitMs());
179379
- if (publishQuestionFrame(frame), !await receipt)
179380
- return process.env.SEMA_DEBUG && console.error(
179381
- `[sema][activeRunRunningChoice] question frame ${questionId} published but no presentation receipt arrived within ${String(gateArmedWaitMs())}ms \u2014 treating it as no decision`
179382
- ), retire(), NOT_PRESENTED;
179383
- counted = !0, openCardCount += 1;
179384
- let watchdog = watchdogToNoAnswer(spec.onNoAnswer), abortWatch = abortToNoAnswer(spec.signal, spec.onNoAnswer), choice;
179385
- try {
179386
- choice = await Promise.race([decided, abortWatch.promise, watchdog.promise]);
179387
- } finally {
179388
- watchdog.cancel(), abortWatch.cancel();
179389
- }
179390
- return retire(), choice;
179391
- }
179392
- function abortToNoAnswer(signal, onNoAnswer) {
179393
- if (signal === void 0) return { promise: new Promise(() => {
179394
- }), cancel: () => {
179395
- } };
179396
- if (signal.aborted) return { promise: Promise.resolve(onNoAnswer), cancel: () => {
179397
- } };
179398
- let onAbort = () => {
179399
- };
179400
- return {
179401
- promise: new Promise((resolve57) => {
179402
- onAbort = () => resolve57(onNoAnswer), signal.addEventListener("abort", onAbort, { once: !0 });
179403
- }),
179404
- cancel: () => {
179405
- try {
179406
- signal.removeEventListener("abort", onAbort);
179407
- } catch {
179408
- }
179409
- }
179410
- };
179411
- }
179412
- function watchdogToNoAnswer(onNoAnswer) {
179413
- let timer2;
179414
- return {
179415
- promise: new Promise((resolve57) => {
179416
- timer2 = setTimeout(() => resolve57(onNoAnswer), choiceAnswerWaitMs());
179417
- }),
179418
- cancel: () => {
179419
- timer2 !== void 0 && clearTimeout(timer2);
179420
- }
179421
- };
179422
- }
179423
- function choiceFromAnswer(answer) {
179424
- let selected = answer?.answers?.[0]?.selected;
179425
- if (!Array.isArray(selected) || selected.length !== 1) return "wait";
179426
- let label = selected[0];
179427
- return label === RUNNING_CHOICE_STEER_LABEL ? "steer" : label === RUNNING_CHOICE_CANCEL_LABEL ? "cancel" : "wait";
179428
- }
179429
- var RUNNING_CHOICE_STEER_LABEL, RUNNING_CHOICE_CANCEL_LABEL, RUNNING_CHOICE_WAIT_LABEL, RUNNING_CHOICE_HEADER, QUESTION_ID_PREFIX, CHOICE_ANSWER_WAIT_MS, answerWaitOverrideMs, cardSeq, offeringByTask, openCardCount, NOT_PRESENTED, init_activeRunRunningChoice = __esm({
179430
- "build-src/src/sema/activeRunRunningChoice.ts"() {
179431
179820
  init_dist();
179432
- init_armedGateRegistry2();
179433
- init_resumeRunningArm();
179434
- RUNNING_CHOICE_STEER_LABEL = "Queue it into the running turn", RUNNING_CHOICE_CANCEL_LABEL = "Cancel that run and send this message now", RUNNING_CHOICE_WAIT_LABEL = "Do nothing for now", RUNNING_CHOICE_HEADER = "Session busy", QUESTION_ID_PREFIX = "active-run:", CHOICE_ANSWER_WAIT_MS = 3e5, answerWaitOverrideMs = null;
179435
- cardSeq = 0, offeringByTask = /* @__PURE__ */ new Set(), openCardCount = 0;
179436
- NOT_PRESENTED = /* @__PURE__ */ Symbol("sema.choiceCardNotPresented");
179821
+ init_interactiveReattach();
179437
179822
  }
179438
179823
  });
179439
179824
 
@@ -184108,7 +184493,16 @@ Fix: run \`sema doctor\` to diagnose, or restart sema to retry.`
184108
184493
  // 无 decide」的 client 过门,在用户按卡之后才 TypeError。类型谓词一次收窄,零散 cast。
184109
184494
  ...(() => {
184110
184495
  let hitl = hasHitlVerbs(client) ? client : null;
184111
- return hitl ? { reopenAskPark: (tid) => reopenAskParkCard(tid, hitl, signal ?? void 0) } : {};
184496
+ return hitl ? {
184497
+ reopenAskPark: (tid) => reopenAskParkCard(tid, hitl, signal ?? void 0),
184498
+ // [3892]-[3899] P0 假死锁防御的复核读口:hasPendingDecision 那个镜像在
184499
+ // --resume 后可能卡脏值(队列里躺着从未渲上屏的幽灵卡 ⇒ 布尔恒真,消息全被
184500
+ // 闸,而引擎侧 run 早已 completed)。selfHeal 在镜像说有卡时向引擎复核
184501
+ // **属主** pending 行数,恰 0 = 脏值正面证据 ⇒ 放行真分诊;归属过滤见
184502
+ // countOwnedPendingApprovals 头注(引擎全局队列有别家陈年行,裸计数恒非零
184503
+ // = 防御恒不触发)。mock 车道无 approvals 面 ⇒ 读口不装 = 旧行为(S-g)。
184504
+ listOwnedPendingApprovals: () => countOwnedPendingApprovals(hitl)
184505
+ } : {};
184112
184506
  })(),
184113
184507
  // C3([3777]/[3779])running 形的三选卡口。**一条用户消息至多一张**(runningChoiceOffered
184114
184508
  // 一次性闸,见声明处):cancel 那条路会触发一次重发,重发再撞 409 时不许再铸卡。
@@ -184235,7 +184629,7 @@ async function probeEngineRunState(taskId) {
184235
184629
  async function probeEngineRunLiveness(taskId, opts) {
184236
184630
  let absent = { state: "unreachable", msSinceLastActivity: null };
184237
184631
  if (typeof taskId != "string" || taskId.length === 0) return absent;
184238
- let get4 = client.runs.get;
184632
+ let getRaw = client.runs.get, get4 = typeof getRaw == "function" ? getRaw.bind(client.runs) : void 0;
184239
184633
  if (typeof get4 != "function") return absent;
184240
184634
  let row2;
184241
184635
  try {
@@ -184264,7 +184658,8 @@ async function cancelEngineRun(taskId) {
184264
184658
  }
184265
184659
  }
184266
184660
  async function attachToResumedRun(taskId) {
184267
- if (typeof client.runs.events != "function") throw new Error(`no durable events verb on this lane (run ${taskId})`);
184661
+ let eventsRaw = client.runs.events;
184662
+ if (typeof (typeof eventsRaw == "function" ? eventsRaw.bind(client.runs) : void 0) != "function") throw new Error(`no durable events verb on this lane (run ${taskId})`);
184268
184663
  let sessionId = getSessionId(), ac = new AbortController(), stream5 = resumeAttachStream(
184269
184664
  taskId,
184270
184665
  {
@@ -184386,6 +184781,7 @@ var BG_TEAR_MAX_RESENDS, BG_RECOVERY_WAIT_MS, DRAINING_MAX_RETRIES, DEFAULT_DRAI
184386
184781
  init_planReviewReopen();
184387
184782
  init_askParkReopen();
184388
184783
  init_activeRunRunningChoice();
184784
+ init_dist();
184389
184785
  init_resumeRunningArm();
184390
184786
  init_transcriptSystemNotice();
184391
184787
  init_interactiveReattach();
@@ -188671,6 +189067,17 @@ var SUPPORTED_RULE_BEHAVIORS, EDITABLE_SOURCES, init_permissionsLoader = __esm({
188671
189067
  });
188672
189068
 
188673
189069
  // build-src/src/utils/permissions/PermissionUpdate.ts
189070
+ var PermissionUpdate_exports = {};
189071
+ __export(PermissionUpdate_exports, {
189072
+ applyPermissionUpdate: () => applyPermissionUpdate,
189073
+ applyPermissionUpdates: () => applyPermissionUpdates,
189074
+ createReadRuleSuggestion: () => createReadRuleSuggestion,
189075
+ extractRules: () => extractRules,
189076
+ hasRules: () => hasRules,
189077
+ persistPermissionUpdate: () => persistPermissionUpdate,
189078
+ persistPermissionUpdates: () => persistPermissionUpdates,
189079
+ supportsPersistence: () => supportsPersistence
189080
+ });
188674
189081
  import { posix as posix2 } from "path";
188675
189082
  function extractRules(updates) {
188676
189083
  return updates ? updates.flatMap((update2) => update2.type === "addRules" ? update2.rules : []) : [];
@@ -191716,6 +192123,7 @@ __export(bashPermissions_exports, {
191716
192123
  MAX_SUGGESTED_RULES_FOR_COMPOUND: () => MAX_SUGGESTED_RULES_FOR_COMPOUND,
191717
192124
  awaitClassifierAutoApproval: () => awaitClassifierAutoApproval,
191718
192125
  bashPermissionRule: () => bashPermissionRule,
192126
+ bashRuleContentHasDangerousBarePrefix: () => bashRuleContentHasDangerousBarePrefix,
191719
192127
  bashToolCheckExactMatchPermission: () => bashToolCheckExactMatchPermission,
191720
192128
  bashToolCheckPermission: () => bashToolCheckPermission,
191721
192129
  bashToolHasPermission: () => bashToolHasPermission,
@@ -191767,6 +192175,14 @@ function getFirstWordPrefix(command8) {
191767
192175
  let cmd = tokens[i];
191768
192176
  return !cmd || !/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(cmd) || BARE_SHELL_PREFIXES.has(cmd) ? null : cmd;
191769
192177
  }
192178
+ function bashRuleContentHasDangerousBarePrefix(ruleContent) {
192179
+ let first = (ruleContent.endsWith(":*") ? ruleContent.slice(0, -2) : ruleContent).trim().split(/\s+/).filter(Boolean)[0];
192180
+ if (first === void 0) return !1;
192181
+ let lowered = first.toLowerCase();
192182
+ if (BARE_SHELL_PREFIXES.has(lowered)) return !0;
192183
+ let base = lowered.split(/[/\\]/).pop();
192184
+ return !!(base !== void 0 && base !== "" && BARE_SHELL_PREFIXES.has(base) || base !== void 0 && base.endsWith(".exe") && BARE_SHELL_PREFIXES.has(base.slice(0, -4)));
192185
+ }
191770
192186
  function suggestionForExactCommand2(command8) {
191771
192187
  let heredocPrefix = extractPrefixBeforeHeredoc(command8);
191772
192188
  if (heredocPrefix)
@@ -257527,7 +257943,7 @@ function stripUnderlineAnsi(content) {
257527
257943
  }
257528
257944
  var import_compiler_runtime24, React16, import_jsx_runtime27, MAX_JSON_FORMAT_LENGTH, URL_IN_JSON, init_OutputLine = __esm({
257529
257945
  "build-src/src/components/shell/OutputLine.tsx"() {
257530
- import_compiler_runtime24 = __toESM(require_compiler_runtime()), React16 = __toESM(require_react());
257946
+ import_compiler_runtime24 = __toESM(require_compiler_runtime(), 1), React16 = __toESM(require_react(), 1);
257531
257947
  init_useTerminalSize();
257532
257948
  init_ink2();
257533
257949
  init_hyperlink();
@@ -257536,7 +257952,7 @@ var import_compiler_runtime24, React16, import_jsx_runtime27, MAX_JSON_FORMAT_LE
257536
257952
  init_MessageResponse();
257537
257953
  init_messageActions();
257538
257954
  init_ExpandShellOutputContext();
257539
- import_jsx_runtime27 = __toESM(require_jsx_runtime());
257955
+ import_jsx_runtime27 = __toESM(require_jsx_runtime(), 1);
257540
257956
  MAX_JSON_FORMAT_LENGTH = 1e4;
257541
257957
  URL_IN_JSON = /https?:\/\/[^\s"'<>\\]+/g;
257542
257958
  }
@@ -285711,7 +286127,7 @@ function FileEditToolUseRejectedMessage(t0) {
285711
286127
  }
285712
286128
  var import_compiler_runtime43, import_jsx_runtime52, MAX_LINES_TO_RENDER, init_FileEditToolUseRejectedMessage = __esm({
285713
286129
  "build-src/src/components/FileEditToolUseRejectedMessage.tsx"() {
285714
- import_compiler_runtime43 = __toESM(require_compiler_runtime(), 1);
286130
+ import_compiler_runtime43 = __toESM(require_compiler_runtime());
285715
286131
  init_useTerminalSize();
285716
286132
  init_cwd();
285717
286133
  init_ink2();
@@ -285719,7 +286135,7 @@ var import_compiler_runtime43, import_jsx_runtime52, MAX_LINES_TO_RENDER, init_F
285719
286135
  init_MessageResponse();
285720
286136
  init_StructuredDiffList();
285721
286137
  init_stringUtils();
285722
- import_jsx_runtime52 = __toESM(require_jsx_runtime(), 1), MAX_LINES_TO_RENDER = 10;
286138
+ import_jsx_runtime52 = __toESM(require_jsx_runtime()), MAX_LINES_TO_RENDER = 10;
285723
286139
  }
285724
286140
  });
285725
286141
 
@@ -307339,7 +307755,8 @@ async function buildHelper() {
307339
307755
  await execFileP("/usr/libexec/PlistBuddy", ["-c", "Add :LSUIElement bool true", plist]), await execFileP("/usr/libexec/PlistBuddy", ["-c", "Set :CFBundleName Sema", plist]), (await execFileP("/usr/libexec/PlistBuddy", ["-c", "Add :CFBundleIdentifier string com.sema.notifier", plist])).code !== 0 && await execFileP("/usr/libexec/PlistBuddy", ["-c", "Set :CFBundleIdentifier com.sema.notifier", plist]), await execFileP("/usr/bin/touch", [app]);
307340
307756
  try {
307341
307757
  chmodSync4(appletBinaryPath(), 493);
307342
- } catch {
307758
+ } catch (e) {
307759
+ failOpen("mac-notifier-chmod", void 0, String(e));
307343
307760
  }
307344
307761
  return rmSync6(tmp, { recursive: !0, force: !0 }), logForDebugging(`[mac-notifier] helper built at ${app}`), macNotifierReady();
307345
307762
  } catch (e) {
@@ -307359,6 +307776,7 @@ var HELPER_DIR_NAME, APPLET_SOURCE, buildInFlight, SEMA_ICON_512_B64, init_macNo
307359
307776
  "build-src/src/sema/macNotifier.ts"() {
307360
307777
  init_envUtils();
307361
307778
  init_debug();
307779
+ init_failOpen();
307362
307780
  HELPER_DIR_NAME = "Sema Notifier.app", APPLET_SOURCE = `on run argv
307363
307781
  set theTitle to "Sema"
307364
307782
  set theMessage to ""
@@ -308502,7 +308920,7 @@ function TaskListV2({
308502
308920
  visibleTasks2.map((task_0) => /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(TaskItem, { task: task_0, ownerColor: task_0.owner ? teammateColors[task_0.owner] : void 0, openBlockers: task_0.blockedBy.filter((id_3) => unresolvedTaskIds.has(id_3)), activity: task_0.owner ? teammateActivity[task_0.owner] : void 0, ownerActive: task_0.owner ? activeTeammates.has(task_0.owner) : !1, columns }, task_0.id)),
308503
308921
  maxDisplay > 0 && hiddenSummary && /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(ThemedText, { dimColor: !0, children: hiddenSummary })
308504
308922
  ] });
308505
- return isStandalone ? /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)(ThemedBox_default, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [
308923
+ return isStandalone ? /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)(ThemedBox_default, { flexDirection: "column", marginTop: 1, marginLeft: 2, flexShrink: 0, children: [
308506
308924
  /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(ThemedBox_default, { children: /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)(ThemedText, { dimColor: !0, children: [
308507
308925
  /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(ThemedText, { bold: !0, children: tasks3.length }),
308508
308926
  " tasks (",
@@ -308516,7 +308934,7 @@ function TaskListV2({
308516
308934
  " open)"
308517
308935
  ] }) }),
308518
308936
  content
308519
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(ThemedBox_default, { flexDirection: "column", children: content });
308937
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(ThemedBox_default, { flexDirection: "column", flexShrink: 0, children: content });
308520
308938
  }
308521
308939
  function getTaskIcon(status3) {
308522
308940
  switch (status3) {
@@ -316554,7 +316972,7 @@ function useClaudeAiLimits() {
316554
316972
  }
316555
316973
  var import_react69, init_claudeAiLimitsHook = __esm({
316556
316974
  "build-src/src/services/claudeAiLimitsHook.ts"() {
316557
- import_react69 = __toESM(require_react());
316975
+ import_react69 = __toESM(require_react(), 1);
316558
316976
  init_claudeAiLimits();
316559
316977
  }
316560
316978
  });
@@ -395840,6 +396258,7 @@ var import_compiler_runtime174, import_react144, import_jsx_runtime245, init_Plu
395840
396258
  init_PluginErrors();
395841
396259
  init_parseArgs();
395842
396260
  init_ValidatePlugin();
396261
+ init_failOpen();
395843
396262
  import_jsx_runtime245 = __toESM(require_jsx_runtime(), 1);
395844
396263
  }
395845
396264
  });
@@ -399696,21 +400115,20 @@ var sema_brand_default, init_sema_brand = __esm({
399696
400115
  _doc: "displayName/email \u7559 null = \u8FD0\u884C\u65F6\u56DE\u9000(\u540D\u5B57\u53D6\u672C\u673A OS \u7528\u6237\u540D,\u90AE\u7BB1\u4E0D\u663E\u793A)\u3002\u522B\u70E7\u4E2A\u4EBA\u4FE1\u606F\u8FDB\u54C1\u724C\u6587\u4EF6(F-S1-1:\u65B0\u7528\u6237\u66FE\u770B\u5230 Welcome back Clay!)\u3002"
399697
400116
  },
399698
400117
  whatsNew: {
399699
- version: "1.0.74",
400118
+ version: "1.0.76",
399700
400119
  notes: [
399701
- `Reopened approval cards now tell the truth about their own history: a card that's genuinely being shown for the first time no longer claims it was "closed without being answered" (per-session tracking of which cards actually got shown)`,
399702
- "Child-delegated approvals that were parking under the wrong session now surface correctly, with an explicit note on which run they belong to; a decide call that fails in flight gets one short retry before it's reported",
399703
- "In-stream approval requests (design/172) are now consumed end to end \u2014 reconnects and non-sticky replicas that used to miss `approval_request`/`approval_revoke` frames now decide through the durable path instead of stalling",
399704
- `The Permissions panel gained a "Worker policy" page showing the deployment's real, live-enforced autonomy band and command rules \u2014 separate from your local Allow/Ask/Deny/Workspace settings, which can legitimately differ from what the server actually enforces`,
399705
- "File-delivery notices (`SendUserFile`) now show up as a system line in the transcript instead of vanishing silently; subagent resume can target a background agent by its precise call id instead of guessing from a display name",
399706
- `The Fleet panel's terminal-state fallback now also watches the idle-period notification channel, not just the in-turn one \u2014 background subagents that finish after a turn has already ended no longer get stuck showing "running" forever`,
399707
- "Engine 7.14.0 rides in (core ^5.27.0 / SDK ^6.15.0). Deployment-facing BREAKING for SQL-backed installs only: upgrading past this engine on a Postgres/MySQL-backed deployment requires `DROP TABLE tool_result` before boot (the engine refuses to start otherwise, with the exact recovery command in the error). Local single-machine installs (the default for this shell) are unaffected \u2014 they use the file-backed store, not the SQL one.",
399708
- "Headless (`-p`) runs now refuse to attach to an already-running engine that can't be confirmed permission-gate-aware (missing capability or a failed probe), instead of silently running ungated; interactive sessions still warn and proceed",
399709
- "`sema cloud images list/show --json` now exits non-zero when the deployment honestly has no image backend configured, instead of always exiting 0 on that path"
400120
+ "The agents panel no longer opens mid-turn: while a turn is in flight, the left-arrow key stays with the composer, and the footer hint plus the background-agent placeholder only advertise \u2190 when the prompt is idle and empty \u2014 the panel can no longer yank the screen away from a streaming turn",
400121
+ "ctrl+b (send the current work to the background) no longer takes the turn's already-backgrounded subagents down with it",
400122
+ "Resuming a session with a parked approval no longer risks a false deadlock: the ownership recheck that guards card re-presentation now recognizes runs by their persisted task id as well as the in-process ledger, so a first-turn park \u2192 kill \u2192 resume re-presents exactly one live card",
400123
+ `Approval-card rule suggestions got stricter and more honest: interpreter-style commands (python, node, npx, eval, exec, ssh and friends) are never offered as persistent allow rules in any spelling, and "won't ask again" is only claimed when the rule took effect live \u2014 otherwise the card says it fully applies after restart`,
400124
+ "Background subagents stream their transcript into the viewing pane while they run (per-agent detail stream), and the idle placeholder points at the agents panel instead of a stale hint",
400125
+ "Task list rendering no longer collapses rows in fullscreen (alt-screen) terminals",
400126
+ "Durable run controls from a reconnected session no longer fail due to a lost client binding on the run verbs",
400127
+ "Engine unchanged at 7.19.0 (same pin as 1.0.75); this release is a shell-side interaction-safety and correctness batch"
399710
400128
  ]
399711
400129
  },
399712
- productVersion: "1.0.75",
399713
- announcement: "sema 1.0.75 \u2014 Decision-wall correctness batch: plan-review cards route by what the row actually asks (no more silent false answers when a Write approval wore a human-gate label), rows that have not been minted yet get a bounded birth-wait instead of being misread as already decided, and resume/cold-start now reconciles parked approvals with an honest re-present path. Approval decision notes ride the wire three-state (recorded / not recorded / unknown) so the audit line never echoes a note the store dropped. Subagent detail streams are consumed live, and the task list renders with CC-parity windowing (5-row window, completed fade, collapsed tail). Engine 7.19.0 rides in (core ^5.31.0 / SDK ^6.17.2); the background-monitor reversibility probe now fail-closes on glob operands upstream \u2014 background shell probes ask instead of guessing."
400130
+ productVersion: "1.0.76",
400131
+ announcement: `sema 1.0.76 \u2014 Interaction-safety batch: the agents panel no longer opens mid-turn (\u2190 stays with the composer while a turn is in flight, and every hint that advertises it follows the same predicate), ctrl+b no longer takes backgrounded subagents down with it, and resume ownership rechecks recognize persisted task ids so parked approvals re-present exactly once instead of dead-locking. Approval-card rule suggestions refuse interpreter-family commands in every spelling and only claim "won't ask again" when the rule applied live. Background subagents stream their transcripts into the viewer, and fullscreen task lists render without row collapse. Engine unchanged at 7.19.0.`
399714
400132
  };
399715
400133
  }
399716
400134
  });
@@ -403312,7 +403730,8 @@ function _temp230(url3) {
403312
403730
  if (url3.startsWith("file:"))
403313
403731
  try {
403314
403732
  openPath(fileURLToPath7(url3));
403315
- } catch {
403733
+ } catch (e) {
403734
+ failOpen("fullscreen-open-file-url", void 0, String(e));
403316
403735
  }
403317
403736
  else
403318
403737
  openBrowser(url3);
@@ -403387,6 +403806,7 @@ var import_compiler_runtime191, import_react160, import_jsx_runtime270, MODAL_TR
403387
403806
  init_stringUtils();
403388
403807
  init_nullRenderingAttachments();
403389
403808
  init_PromptInputFooterSuggestions();
403809
+ init_failOpen();
403390
403810
  import_jsx_runtime270 = __toESM(require_jsx_runtime(), 1), MODAL_TRANSCRIPT_PEEK = 2, ScrollChromeContext = (0, import_react160.createContext)({
403391
403811
  setStickyPrompt: () => {
403392
403812
  }
@@ -403911,21 +404331,20 @@ var require_sema_brand = __commonJS({
403911
404331
  _doc: "displayName/email \u7559 null = \u8FD0\u884C\u65F6\u56DE\u9000(\u540D\u5B57\u53D6\u672C\u673A OS \u7528\u6237\u540D,\u90AE\u7BB1\u4E0D\u663E\u793A)\u3002\u522B\u70E7\u4E2A\u4EBA\u4FE1\u606F\u8FDB\u54C1\u724C\u6587\u4EF6(F-S1-1:\u65B0\u7528\u6237\u66FE\u770B\u5230 Welcome back Clay!)\u3002"
403912
404332
  },
403913
404333
  whatsNew: {
403914
- version: "1.0.74",
404334
+ version: "1.0.76",
403915
404335
  notes: [
403916
- `Reopened approval cards now tell the truth about their own history: a card that's genuinely being shown for the first time no longer claims it was "closed without being answered" (per-session tracking of which cards actually got shown)`,
403917
- "Child-delegated approvals that were parking under the wrong session now surface correctly, with an explicit note on which run they belong to; a decide call that fails in flight gets one short retry before it's reported",
403918
- "In-stream approval requests (design/172) are now consumed end to end \u2014 reconnects and non-sticky replicas that used to miss `approval_request`/`approval_revoke` frames now decide through the durable path instead of stalling",
403919
- `The Permissions panel gained a "Worker policy" page showing the deployment's real, live-enforced autonomy band and command rules \u2014 separate from your local Allow/Ask/Deny/Workspace settings, which can legitimately differ from what the server actually enforces`,
403920
- "File-delivery notices (`SendUserFile`) now show up as a system line in the transcript instead of vanishing silently; subagent resume can target a background agent by its precise call id instead of guessing from a display name",
403921
- `The Fleet panel's terminal-state fallback now also watches the idle-period notification channel, not just the in-turn one \u2014 background subagents that finish after a turn has already ended no longer get stuck showing "running" forever`,
403922
- "Engine 7.14.0 rides in (core ^5.27.0 / SDK ^6.15.0). Deployment-facing BREAKING for SQL-backed installs only: upgrading past this engine on a Postgres/MySQL-backed deployment requires `DROP TABLE tool_result` before boot (the engine refuses to start otherwise, with the exact recovery command in the error). Local single-machine installs (the default for this shell) are unaffected \u2014 they use the file-backed store, not the SQL one.",
403923
- "Headless (`-p`) runs now refuse to attach to an already-running engine that can't be confirmed permission-gate-aware (missing capability or a failed probe), instead of silently running ungated; interactive sessions still warn and proceed",
403924
- "`sema cloud images list/show --json` now exits non-zero when the deployment honestly has no image backend configured, instead of always exiting 0 on that path"
404336
+ "The agents panel no longer opens mid-turn: while a turn is in flight, the left-arrow key stays with the composer, and the footer hint plus the background-agent placeholder only advertise \u2190 when the prompt is idle and empty \u2014 the panel can no longer yank the screen away from a streaming turn",
404337
+ "ctrl+b (send the current work to the background) no longer takes the turn's already-backgrounded subagents down with it",
404338
+ "Resuming a session with a parked approval no longer risks a false deadlock: the ownership recheck that guards card re-presentation now recognizes runs by their persisted task id as well as the in-process ledger, so a first-turn park \u2192 kill \u2192 resume re-presents exactly one live card",
404339
+ `Approval-card rule suggestions got stricter and more honest: interpreter-style commands (python, node, npx, eval, exec, ssh and friends) are never offered as persistent allow rules in any spelling, and "won't ask again" is only claimed when the rule took effect live \u2014 otherwise the card says it fully applies after restart`,
404340
+ "Background subagents stream their transcript into the viewing pane while they run (per-agent detail stream), and the idle placeholder points at the agents panel instead of a stale hint",
404341
+ "Task list rendering no longer collapses rows in fullscreen (alt-screen) terminals",
404342
+ "Durable run controls from a reconnected session no longer fail due to a lost client binding on the run verbs",
404343
+ "Engine unchanged at 7.19.0 (same pin as 1.0.75); this release is a shell-side interaction-safety and correctness batch"
403925
404344
  ]
403926
404345
  },
403927
- productVersion: "1.0.75",
403928
- announcement: "sema 1.0.75 \u2014 Decision-wall correctness batch: plan-review cards route by what the row actually asks (no more silent false answers when a Write approval wore a human-gate label), rows that have not been minted yet get a bounded birth-wait instead of being misread as already decided, and resume/cold-start now reconciles parked approvals with an honest re-present path. Approval decision notes ride the wire three-state (recorded / not recorded / unknown) so the audit line never echoes a note the store dropped. Subagent detail streams are consumed live, and the task list renders with CC-parity windowing (5-row window, completed fade, collapsed tail). Engine 7.19.0 rides in (core ^5.31.0 / SDK ^6.17.2); the background-monitor reversibility probe now fail-closes on glob operands upstream \u2014 background shell probes ask instead of guessing."
404346
+ productVersion: "1.0.76",
404347
+ announcement: `sema 1.0.76 \u2014 Interaction-safety batch: the agents panel no longer opens mid-turn (\u2190 stays with the composer while a turn is in flight, and every hint that advertises it follows the same predicate), ctrl+b no longer takes backgrounded subagents down with it, and resume ownership rechecks recognize persisted task ids so parked approvals re-present exactly once instead of dead-locking. Approval-card rule suggestions refuse interpreter-family commands in every spelling and only claim "won't ask again" when the rule applied live. Background subagents stream their transcripts into the viewer, and fullscreen task lists render without row collapse. Engine unchanged at 7.19.0.`
403929
404348
  };
403930
404349
  }
403931
404350
  });
@@ -407694,7 +408113,7 @@ function livePlaceholderUuid(taskId) {
407694
408113
  }
407695
408114
  function createLivePlaceholderMessage(taskId) {
407696
408115
  return { ...createAssistantMessage({
407697
- content: "*waiting for agent output \u2014 streamed transcript appears here when available; a background agent shows tool/token progress in the agents tree and lands its outcome here on completion*",
408116
+ content: "*waiting for agent output \u2014 streamed transcript appears here when available; a background agent shows tool/token progress in the agents tree and lands its outcome here on completion. When idle with the prompt empty, press \u2190 for agents to follow its live tool/token detail meanwhile.*",
407698
408117
  isVirtual: !0
407699
408118
  }), uuid: livePlaceholderUuid(taskId) };
407700
408119
  }
@@ -412398,6 +412817,23 @@ function surfaceRulePersistOutcome(ack) {
412398
412817
  logForDebugging(`persistedRulesWire: surfacing rule-persist outcome failed (non-fatal): ${String(e)}`);
412399
412818
  }
412400
412819
  }
412820
+ function surfaceLocalRulePersistOutcome(outcome) {
412821
+ let text2 = outcome.ok ? outcome.appliedLive ? "Saved: won't ask again for this rule" : "Saved to settings \u2014 fully applies after restart (this session may ask once more)" : `"Don't ask again" was not saved (${outcome.error}) \u2014 the one-time approval is still being submitted`;
412822
+ try {
412823
+ surfaceWireNotice(
412824
+ {
412825
+ key: RULE_PERSIST_NOTICE_KEY,
412826
+ text: text2,
412827
+ color: "warning",
412828
+ priority: "immediate",
412829
+ timeoutMs: rulePersistNoticeMs
412830
+ },
412831
+ "preempt"
412832
+ );
412833
+ } catch (e) {
412834
+ logForDebugging(`persistedRulesWire: surfacing local rule-persist outcome failed (non-fatal): ${String(e)}`);
412835
+ }
412836
+ }
412401
412837
  var facadeOverride, PAGE_LIMIT, MAX_PAGES, CC_DIR4, LAYER_BYTE_CAP, RULE_PERSIST_NOTICE_KEY, RULE_PERSIST_NOTICE_DEFAULT_MS, rulePersistNoticeMs, init_persistedRulesWire = __esm({
412402
412838
  "build-src/src/sema/persistedRulesWire.ts"() {
412403
412839
  init_main2();
@@ -416537,7 +416973,8 @@ function ErrorsTabContent({
416537
416973
  try {
416538
416974
  let config4 = await loadKnownMarketplacesConfig(), { failures } = await loadMarketplacesWithGracefulDegradation(config4);
416539
416975
  setMarketplaceLoadFailures(failures);
416540
- } catch {
416976
+ } catch (e) {
416977
+ failOpen("cmd-plugin-marketplace-load", void 0, String(e));
416541
416978
  }
416542
416979
  })();
416543
416980
  }, []);
@@ -416985,6 +417422,7 @@ var import_react185, import_jsx_runtime326, TAG_USAGE, TRANSIENT_ERROR_TYPES, in
416985
417422
  init_PluginErrors();
416986
417423
  init_parseArgs();
416987
417424
  init_ValidatePlugin();
417425
+ init_failOpen();
416988
417426
  import_jsx_runtime326 = __toESM(require_jsx_runtime(), 1);
416989
417427
  TAG_USAGE = `Usage: /plugin tag [path] [--push] [--dry-run] [-f|--force]
416990
417428
 
@@ -466951,7 +467389,13 @@ function createFleetViewControl() {
466951
467389
  return process.env.SEMA_NO_LEFTARROW_AGENTS !== "1";
466952
467390
  },
466953
467391
  canOpenFromComposer() {
466954
- return this.leftArrowOpensAgents() && !_isOpen;
467392
+ return this.leftArrowOpensAgents() && !_isOpen && !_turnInFlight;
467393
+ },
467394
+ setTurnInFlight(inFlight4) {
467395
+ _turnInFlight = !!inFlight4;
467396
+ },
467397
+ turnInFlight() {
467398
+ return _turnInFlight;
466955
467399
  },
466956
467400
  isComposerEmpty() {
466957
467401
  return _composerEmpty;
@@ -466975,9 +467419,9 @@ function createFleetViewControl() {
466975
467419
  }
466976
467420
  };
466977
467421
  }
466978
- var _composerEmpty, g3, fleetViewControl, fleetViewControl_default, init_fleetViewControl = __esm({
467422
+ var _composerEmpty, _turnInFlight, g3, fleetViewControl, fleetViewControl_default, init_fleetViewControl = __esm({
466979
467423
  "build-src/src/sema/fleetViewControl.ts"() {
466980
- _composerEmpty = !0;
467424
+ _composerEmpty = !0, _turnInFlight = !1;
466981
467425
  g3 = globalThis, fleetViewControl = g3.__semaFleetView ?? (g3.__semaFleetView = createFleetViewControl()), fleetViewControl_default = fleetViewControl;
466982
467426
  }
466983
467427
  });
@@ -469434,9 +469878,136 @@ var NUMERIC, ENV_VAR, WRAPPER_COMMANDS, toArray4, init_prefix2 = __esm({
469434
469878
  }
469435
469879
  });
469436
469880
 
469881
+ // build-src/src/sema/localAllowRuleWrite.ts
469882
+ function parseLocalAllowRule(rule, expectedToolName) {
469883
+ if (typeof rule != "string" || rule === "")
469884
+ return { ok: !1, error: "rule text is empty" };
469885
+ let value;
469886
+ try {
469887
+ value = permissionRuleValueFromString(rule);
469888
+ } catch (e) {
469889
+ return { ok: !1, error: `rule text did not parse (${String(e)})` };
469890
+ }
469891
+ if (typeof value.toolName != "string" || value.toolName === "")
469892
+ return { ok: !1, error: "rule text carries no tool name" };
469893
+ if (value.ruleContent === void 0 || value.ruleContent === "")
469894
+ return {
469895
+ ok: !1,
469896
+ error: "candidate is a whole-tool allow rule (broader than this ask) \u2014 not offered locally"
469897
+ };
469898
+ if (expectedToolName !== void 0 && value.toolName !== expectedToolName)
469899
+ return {
469900
+ ok: !1,
469901
+ error: `candidate names tool "${value.toolName}" but this ask is for "${expectedToolName}" \u2014 a rule for another tool would never match`
469902
+ };
469903
+ if (!hasLiteralAnchor(value.ruleContent))
469904
+ return {
469905
+ ok: !1,
469906
+ error: "candidate is all wildcard (matches every command for this tool) \u2014 equivalent to a whole-tool allow, not offered locally"
469907
+ };
469908
+ if (value.toolName === BASH_TOOL_NAME_FOR_RULES && bashPermissionsModule().bashRuleContentHasDangerousBarePrefix(value.ruleContent))
469909
+ return {
469910
+ ok: !1,
469911
+ error: "candidate is a bare interpreter/wrapper prefix (bash/sh/env/sudo/\u2026) \u2014 persisting it would authorize arbitrary commands, not offered locally"
469912
+ };
469913
+ {
469914
+ let setup2 = permissionSetupModule2();
469915
+ if (setup2.isDangerousBashPermission(value.toolName, value.ruleContent) || setup2.isDangerousPowerShellPermission(value.toolName, value.ruleContent))
469916
+ return {
469917
+ ok: !1,
469918
+ error: "candidate allow-rule matches a code-execution interpreter/wrapper pattern (python/node/eval/ssh/\u2026) \u2014 persisting it would authorize arbitrary code, not offered locally"
469919
+ };
469920
+ }
469921
+ return { ok: !0, value, canonical: permissionRuleValueToString(value) };
469922
+ }
469923
+ function localAllowRulesPermittedByPolicy() {
469924
+ try {
469925
+ return permissionsLoaderModule().shouldShowAlwaysAllowOptions();
469926
+ } catch {
469927
+ return !1;
469928
+ }
469929
+ }
469930
+ function hasLiteralAnchor(ruleContent) {
469931
+ let out6 = "";
469932
+ for (let i = 0; i < ruleContent.length; i++) {
469933
+ let ch2 = ruleContent[i];
469934
+ if (ch2 === "\\" && i + 1 < ruleContent.length) {
469935
+ out6 += ruleContent[i + 1], i++;
469936
+ continue;
469937
+ }
469938
+ ch2 !== "*" && (out6 += ch2);
469939
+ }
469940
+ return out6.trim() !== "";
469941
+ }
469942
+ function permissionUpdateModule() {
469943
+ return init_PermissionUpdate(), __toCommonJS(PermissionUpdate_exports);
469944
+ }
469945
+ function permissionsLoaderModule() {
469946
+ return init_permissionsLoader(), __toCommonJS(permissionsLoader_exports);
469947
+ }
469948
+ function bashPermissionsModule() {
469949
+ return init_bashPermissions(), __toCommonJS(bashPermissions_exports);
469950
+ }
469951
+ function permissionSetupModule2() {
469952
+ return init_permissionSetup(), __toCommonJS(permissionSetup_exports);
469953
+ }
469954
+ function leaderBridgeModule() {
469955
+ return init_leaderPermissionBridge(), __toCommonJS(leaderPermissionBridge_exports);
469956
+ }
469957
+ function persistLocalAllowRule(rule) {
469958
+ let parsed = parseLocalAllowRule(rule);
469959
+ if (!parsed.ok)
469960
+ return logForDebugging(`localAllowRuleWrite: refusing to persist a rule candidate \u2014 ${parsed.error}`), { ok: !1, error: parsed.error };
469961
+ let update2 = {
469962
+ type: "addRules",
469963
+ rules: [parsed.value],
469964
+ behavior: "allow",
469965
+ destination: "localSettings"
469966
+ };
469967
+ if (!localAllowRulesPermittedByPolicy()) {
469968
+ let error51 = `managed policy allows only managed permission rules \u2014 this deployment cannot save a local "don't ask again" rule`;
469969
+ return logForDebugging(`localAllowRuleWrite: ${error51}`), { ok: !1, error: error51 };
469970
+ }
469971
+ let mod = permissionUpdateModule(), written = !1;
469972
+ try {
469973
+ written = permissionsLoaderModule().addPermissionRulesToSettings(
469974
+ { ruleValues: [parsed.value], ruleBehavior: "allow" },
469975
+ "localSettings"
469976
+ );
469977
+ } catch (e) {
469978
+ let error51 = `writing the rule to local settings threw (${String(e)})`;
469979
+ return logForDebugging(`localAllowRuleWrite: ${error51}`), { ok: !1, error: error51 };
469980
+ }
469981
+ if (!written) {
469982
+ let error51 = "local settings refused the rule (managed-rules-only policy, or a settings parse/lock/write failure) \u2014 nothing was saved";
469983
+ return logForDebugging(`localAllowRuleWrite: ${error51}`), { ok: !1, error: error51 };
469984
+ }
469985
+ try {
469986
+ let setCtx = leaderBridgeModule().getLeaderSetToolPermissionContext(), state4 = getAppStateStoreRef()?.getState?.();
469987
+ if (setCtx && state4?.toolPermissionContext)
469988
+ return setCtx(mod.applyPermissionUpdate(state4.toolPermissionContext, update2)), { ok: !0, appliedLive: !0 };
469989
+ logForDebugging(
469990
+ "localAllowRuleWrite: rule persisted to local settings, but this lane has no in-memory permission context to update (it will take effect on next start)"
469991
+ );
469992
+ } catch (e) {
469993
+ logForDebugging(
469994
+ `localAllowRuleWrite: rule persisted, but applying it to the in-memory permission context failed (non-fatal): ${String(e)}`
469995
+ );
469996
+ }
469997
+ return { ok: !0, appliedLive: !1 };
469998
+ }
469999
+ var BASH_TOOL_NAME_FOR_RULES, init_localAllowRuleWrite = __esm({
470000
+ "build-src/src/sema/localAllowRuleWrite.ts"() {
470001
+ init_permissionRuleParser();
470002
+ init_debug();
470003
+ init_appStateRef();
470004
+ BASH_TOOL_NAME_FOR_RULES = "Bash";
470005
+ }
470006
+ });
470007
+
469437
470008
  // build-src/src/components/permissions/engineRuleSuggestions.tsx
469438
- function readSuggestions(confirm2) {
469439
- let raw2 = confirm2.ruleSuggestions;
470009
+ function readSuggestions(confirm2, key = "ruleSuggestions") {
470010
+ let raw2 = confirm2[key];
469440
470011
  if (!Array.isArray(raw2)) return [];
469441
470012
  let out6 = [];
469442
470013
  for (let item of raw2) {
@@ -469472,6 +470043,41 @@ function tryEngineRulePersistSelection(confirm2, value, onDone) {
469472
470043
  let onSelected = confirm2.onPersistRuleSelected;
469473
470044
  return onSelected?.(rule), onDone?.(), confirm2.onAllow(confirm2.input, []), !0;
469474
470045
  }
470046
+ function readLocalRedeemable(confirm2) {
470047
+ let expected = typeof confirm2.tool?.name == "string" ? confirm2.tool.name : void 0, out6 = [];
470048
+ for (let s of readSuggestions(confirm2, "ruleSuggestionsReadOnly")) {
470049
+ let parsed = parseLocalAllowRule(s.rule, expected);
470050
+ parsed.ok && out6.push({ rule: s.rule, canonical: parsed.canonical });
470051
+ }
470052
+ return out6;
470053
+ }
470054
+ function localRuleSuggestionOptions(confirm2) {
470055
+ if (!localAllowRulesPermittedByPolicy()) return [];
470056
+ let suggestions = readLocalRedeemable(confirm2);
470057
+ if (suggestions.length === 0) return [];
470058
+ let scope = getOriginalCwd();
470059
+ return suggestions.map((s, i) => ({
470060
+ label: /* @__PURE__ */ (0, import_jsx_runtime378.jsxs)(ThemedText, { children: [
470061
+ "Yes, and don't ask again for ",
470062
+ /* @__PURE__ */ (0, import_jsx_runtime378.jsx)(ThemedText, { bold: !0, children: s.canonical }),
470063
+ " in ",
470064
+ /* @__PURE__ */ (0, import_jsx_runtime378.jsx)(ThemedText, { bold: !0, children: scope })
470065
+ ] }),
470066
+ value: `${LOCAL_RULE_OPTION_VALUE_PREFIX}${i}`
470067
+ }));
470068
+ }
470069
+ function localRuleForOptionValue(confirm2, value) {
470070
+ if (!value.startsWith(LOCAL_RULE_OPTION_VALUE_PREFIX)) return;
470071
+ let idx = Number(value.slice(LOCAL_RULE_OPTION_VALUE_PREFIX.length));
470072
+ if (!(!Number.isInteger(idx) || idx < 0))
470073
+ return readLocalRedeemable(confirm2)[idx]?.rule;
470074
+ }
470075
+ function tryLocalRulePersistSelection(confirm2, value, onDone) {
470076
+ let rule = localRuleForOptionValue(confirm2, value);
470077
+ if (rule === void 0) return !1;
470078
+ let onSelected = confirm2.onPersistLocalRuleSelected;
470079
+ return onSelected?.(rule), onDone?.(), confirm2.onAllow(confirm2.input, []), !0;
470080
+ }
469475
470081
  function PersistedRuleShadowedRow({ confirm: confirm2 }) {
469476
470082
  let shadowed = confirm2.persistedRuleShadowed;
469477
470083
  return typeof shadowed != "string" || shadowed === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime378.jsxs)(ThemedText, { dimColor: !0, children: [
@@ -469480,11 +470086,13 @@ function PersistedRuleShadowedRow({ confirm: confirm2 }) {
469480
470086
  ") still exists \u2014 it just cannot clear this particular call, so this one is confirmed individually."
469481
470087
  ] });
469482
470088
  }
469483
- var import_jsx_runtime378, ENGINE_RULE_OPTION_VALUE_PREFIX, init_engineRuleSuggestions = __esm({
470089
+ var import_jsx_runtime378, ENGINE_RULE_OPTION_VALUE_PREFIX, LOCAL_RULE_OPTION_VALUE_PREFIX, init_engineRuleSuggestions = __esm({
469484
470090
  "build-src/src/components/permissions/engineRuleSuggestions.tsx"() {
469485
470091
  init_ink2();
469486
470092
  init_state();
470093
+ init_localAllowRuleWrite();
469487
470094
  import_jsx_runtime378 = __toESM(require_jsx_runtime(), 1), ENGINE_RULE_OPTION_VALUE_PREFIX = "sema-engine-rule:";
470095
+ LOCAL_RULE_OPTION_VALUE_PREFIX = "sema-local-rule:";
469488
470096
  }
469489
470097
  });
469490
470098
 
@@ -470938,14 +471546,17 @@ function FilePermissionDialog({
470938
471546
  ideName
470939
471547
  } = useDiffInIDE(diffParams), onChange = (option_0, feedback2) => {
470940
471548
  closeTabInIDE2?.(), fileDialogResult.onChange(option_0, parsedInput, feedback2?.trim());
470941
- }, shadowedRow = toolUseConfirm.persistedRuleShadowed !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(PersistedRuleShadowedRow, { confirm: toolUseConfirm }) : null, ruleOptions = engineRuleSuggestionOptions(toolUseConfirm), optionsWithRules = ruleOptions.length > 0 ? [...options.slice(0, -1), ...ruleOptions.map((o) => ({
471549
+ }, shadowedRow = toolUseConfirm.persistedRuleShadowed !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(PersistedRuleShadowedRow, { confirm: toolUseConfirm }) : null, ruleOptions = [...engineRuleSuggestionOptions(toolUseConfirm), ...localRuleSuggestionOptions(toolUseConfirm)], optionsWithRules = ruleOptions.length > 0 ? [...options.slice(0, -1), ...ruleOptions.map((o) => ({
470942
471550
  ...o,
470943
471551
  option: {
470944
471552
  type: "accept-once"
470945
471553
  }
470946
- })), ...options.slice(-1)] : options, handleEngineRuleValue = (value) => tryEngineRulePersistSelection(toolUseConfirm, value, () => {
470947
- closeTabInIDE2?.(), onDone();
470948
- });
471554
+ })), ...options.slice(-1)] : options, handleEngineRuleValue = (value) => {
471555
+ let done = () => {
471556
+ closeTabInIDE2?.(), onDone();
471557
+ };
471558
+ return tryEngineRulePersistSelection(toolUseConfirm, value, done) || tryLocalRulePersistSelection(toolUseConfirm, value, done);
471559
+ };
470949
471560
  if (showingDiffInIDE && ideDiffConfig && path28)
470950
471561
  return /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(ShowInIDEPrompt, { onChange: (option_1, _input, feedback_0) => onChange(option_1, feedback_0), onSelectValue: handleEngineRuleValue, extraNote: shadowedRow, options: optionsWithRules, filePath: path28, input: parsedInput, ideName, symlinkTarget, rejectFeedback, acceptFeedback, setFocusedOption, onInputModeToggle: handleInputModeToggle, focusedOption, yesInputMode, noInputMode });
470951
471562
  let isSymlinkOutsideCwd = symlinkTarget != null && relative31(getCwd(), symlinkTarget).startsWith(".."), symlinkWarning = symlinkTarget ? /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(ThemedBox_default, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(ThemedText, { color: "warning", children: isSymlinkOutsideCwd ? `This will modify ${symlinkTarget} (outside working directory) via a symlink` : `Symlink target: ${symlinkTarget}` }) }) : null, wireNote = toolUseConfirm.wireNote, wireNoteRow = typeof wireNote == "string" && wireNote.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(ThemedBox_default, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime384.jsx)(ThemedText, { dimColor: !0, children: wireNote }) }) : null;
@@ -471552,7 +472163,7 @@ function BashPermissionRequestInner({
471552
472163
  editablePrefix,
471553
472164
  onEditablePrefixChange
471554
472165
  }), [toolUseConfirm, classifierDescription, initialClassifierDescriptionEmpty, existingAllowDescriptions, yesInputMode, noInputMode, editablePrefix, onEditablePrefixChange]), engineRuleOptionsAppended = (base) => {
471555
- let extra = engineRuleSuggestionOptions(toolUseConfirm);
472166
+ let extra = [...engineRuleSuggestionOptions(toolUseConfirm), ...localRuleSuggestionOptions(toolUseConfirm)];
471556
472167
  return extra.length === 0 ? base : [...base.slice(0, -1), ...extra, ...base.slice(-1)];
471557
472168
  }, handleToggleDebug = (0, import_react223.useCallback)(() => {
471558
472169
  setShowPermissionDebug((prev) => !prev);
@@ -471568,7 +472179,7 @@ function BashPermissionRequestInner({
471568
472179
  isActive: !1
471569
472180
  });
471570
472181
  function onSelect(value_0) {
471571
- if (tryEngineRulePersistSelection(toolUseConfirm, value_0, onDone))
472182
+ if (tryEngineRulePersistSelection(toolUseConfirm, value_0, onDone) || tryLocalRulePersistSelection(toolUseConfirm, value_0, onDone))
471572
472183
  return;
471573
472184
  ({
471574
472185
  yes: 1,
@@ -472339,7 +472950,7 @@ function FallbackPermissionRequest(t0) {
472339
472950
  }, $3[4] = t2) : t2 = $3[4], usePermissionRequestLogging(toolUseConfirm, t2);
472340
472951
  let t3;
472341
472952
  $3[5] !== onDone || $3[6] !== onReject || $3[7] !== toolUseConfirm ? (t3 = (value, feedback2) => {
472342
- if (!tryEngineRulePersistSelection(toolUseConfirm, value, onDone))
472953
+ if (!tryEngineRulePersistSelection(toolUseConfirm, value, onDone) && !tryLocalRulePersistSelection(toolUseConfirm, value, onDone))
472343
472954
  bb8: switch (value) {
472344
472955
  case "yes": {
472345
472956
  logUnaryEvent({
@@ -472473,7 +473084,7 @@ function FallbackPermissionRequest(t0) {
472473
473084
  ] }), $3[44] = toolUseConfirm.permissionResult, $3[45] = t17) : t17 = $3[45];
472474
473085
  let t18;
472475
473086
  $3[46] !== handleCancel || $3[47] !== handleSelect || $3[48] !== options || $3[49] !== toolAnalyticsContext ? (t18 = /* @__PURE__ */ (0, import_jsx_runtime391.jsx)(PermissionPrompt, { options: (() => {
472476
- let extra = engineRuleSuggestionOptions(toolUseConfirm);
473087
+ let extra = [...engineRuleSuggestionOptions(toolUseConfirm), ...localRuleSuggestionOptions(toolUseConfirm)];
472477
473088
  return extra.length === 0 ? options : [...options.slice(0, -1), ...extra, ...options.slice(-1)];
472478
473089
  })(), onSelect: handleSelect, onCancel: handleCancel, toolAnalyticsContext, defaultFocusValue: toolUseConfirm.governanceForced === !0 ? "no" : void 0 }), $3[46] = handleCancel, $3[47] = handleSelect, $3[48] = options, $3[49] = toolAnalyticsContext, $3[50] = t18) : t18 = $3[50];
472479
473090
  let t19;
@@ -491185,7 +491796,7 @@ async function reconcileResumePendingApprovals(deps2) {
491185
491796
  debug3("an owned pending row carries no taskId handle \u2014 both reopen arms address by taskId, nothing to reopen"), verdict.skipped.push({ taskId: null, reason: "no-task-handle" });
491186
491797
  continue;
491187
491798
  }
491188
- let kind = gateKindOf(row2), arm = kind === null ? null : ASK_PARK_GATE_KINDS2.includes(kind) ? "ask" : PLAN_REVIEW_GATE_KINDS.includes(kind) ? "plan" : null;
491799
+ let kind = gateKindOf(row2), arm = kind === null ? null : ASK_PARK_GATE_KINDS2.includes(kind) ? "ask" : PLAN_REVIEW_GATE_KINDS2.includes(kind) ? "plan" : null;
491189
491800
  if (arm === null) {
491190
491801
  debug3(
491191
491802
  `owned pending row (task ${taskId}) is parked on gate kind '${kind ?? "<absent>"}' \u2014 not in either triage table, refusing to route it into a known reopen arm`
@@ -491518,6 +492129,28 @@ var import_compiler_runtime299, import_react298, init_useIDEIntegration = __esm(
491518
492129
  }
491519
492130
  });
491520
492131
 
492132
+ // build-src/src/sema/sessionBackgroundArm.ts
492133
+ function engineHostsTheTurn(env5 = process.env) {
492134
+ let live = env5.SEMA_LIVE_BASEURL;
492135
+ return typeof live == "string" && live.length > 0;
492136
+ }
492137
+ function remoteHostsTheTurn(readGlobal = getIsRemoteMode, readPosture = hasRemoteConnection) {
492138
+ try {
492139
+ return readGlobal() || readPosture();
492140
+ } catch {
492141
+ return !0;
492142
+ }
492143
+ }
492144
+ function sessionBackgroundingAvailable(env5 = process.env) {
492145
+ return !engineHostsTheTurn(env5) && !remoteHostsTheTurn();
492146
+ }
492147
+ var init_sessionBackgroundArm = __esm({
492148
+ "build-src/src/sema/sessionBackgroundArm.ts"() {
492149
+ init_state();
492150
+ init_remoteWriteSeam();
492151
+ }
492152
+ });
492153
+
491521
492154
  // build-src/src/components/SessionBackgroundHint.tsx
491522
492155
  function SessionBackgroundHint(t0) {
491523
492156
  let $3 = (0, import_compiler_runtime300.c)(10), {
@@ -491528,11 +492161,11 @@ function SessionBackgroundHint(t0) {
491528
492161
  if (isEnvTruthy(process.env.SEMA_CODE_DISABLE_BACKGROUND_TASKS))
491529
492162
  return;
491530
492163
  let state4 = appStateStore.getState();
491531
- hasForegroundTasks(state4) ? (backgroundAll(() => appStateStore.getState(), setAppState), getGlobalConfig().hasUsedBackgroundTask || saveGlobalConfig(_temp257)) : isLoading && handleDoublePress();
492164
+ hasForegroundTasks(state4) ? (backgroundAll(() => appStateStore.getState(), setAppState), getGlobalConfig().hasUsedBackgroundTask || saveGlobalConfig(_temp257)) : isLoading && sessionBackgroundingAvailable() && handleDoublePress();
491532
492165
  }, $3[0] = appStateStore, $3[1] = handleDoublePress, $3[2] = isLoading, $3[3] = setAppState, $3[4] = t1) : t1 = $3[4];
491533
492166
  let handleBackground = t1, hasForeground = useAppState(hasForegroundTasks), t2;
491534
492167
  $3[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t2 = !0, $3[5] = t2) : t2 = $3[5];
491535
- let t3 = hasForeground || t2 && isLoading, t4;
492168
+ let t3 = hasForeground || t2 && isLoading && sessionBackgroundingAvailable(), t4;
491536
492169
  $3[6] !== t3 ? (t4 = {
491537
492170
  context: "Task",
491538
492171
  isActive: t3
@@ -491558,6 +492191,7 @@ var import_compiler_runtime300, import_react299, import_jsx_runtime452, init_Ses
491558
492191
  init_ink2();
491559
492192
  init_useKeybinding();
491560
492193
  init_useShortcutDisplay();
492194
+ init_sessionBackgroundArm();
491561
492195
  init_AppState();
491562
492196
  init_LocalShellTask();
491563
492197
  init_config();
@@ -494708,7 +495342,7 @@ function _temp158() {
494708
495342
  }
494709
495343
  var import_compiler_runtime308, import_react313, SETTINGS_ERRORS_NOTIFICATION_KEY, init_useSettingsErrors = __esm({
494710
495344
  "build-src/src/hooks/notifs/useSettingsErrors.tsx"() {
494711
- import_compiler_runtime308 = __toESM(require_compiler_runtime()), import_react313 = __toESM(require_react());
495345
+ import_compiler_runtime308 = __toESM(require_compiler_runtime(), 1), import_react313 = __toESM(require_react(), 1);
494712
495346
  init_notifications2();
494713
495347
  init_state();
494714
495348
  init_allErrors();
@@ -497463,7 +498097,11 @@ function REPL({
497463
498097
  abortControllerRef.current = abortController;
497464
498098
  let sendBridgeResultRef = (0, import_react332.useRef)(() => {
497465
498099
  }), restoreMessageSyncRef = (0, import_react332.useRef)(() => {
497466
- }), scrollRef = (0, import_react332.useRef)(null), modalScrollRef = (0, import_react332.useRef)(null), lastUserScrollTsRef = (0, import_react332.useRef)(0), queryGuard = React175.useRef(new QueryGuard()).current, isQueryActive = React175.useSyncExternalStore(queryGuard.subscribe, queryGuard.getSnapshot), [isExternalLoading, setIsExternalLoadingRaw] = React175.useState(remoteSessionConfig?.hasInitialPrompt ?? !1), isLoading = isQueryActive || isExternalLoading, [userInputOnProcessing, setUserInputOnProcessingRaw] = React175.useState(void 0), userInputBaselineRef = React175.useRef(0), userMessagePendingRef = React175.useRef(!1), loadingStartTimeRef = React175.useRef(0), totalPausedMsRef = React175.useRef(0), pauseStartTimeRef = React175.useRef(null);
498100
+ }), scrollRef = (0, import_react332.useRef)(null), modalScrollRef = (0, import_react332.useRef)(null), lastUserScrollTsRef = (0, import_react332.useRef)(0), queryGuard = React175.useRef(new QueryGuard()).current, isQueryActive = React175.useSyncExternalStore(queryGuard.subscribe, queryGuard.getSnapshot), [isExternalLoading, setIsExternalLoadingRaw] = React175.useState(remoteSessionConfig?.hasInitialPrompt ?? !1), isLoading = isQueryActive || isExternalLoading;
498101
+ React175.useEffect(() => {
498102
+ fleetViewControl_default.setTurnInFlight(isLoading);
498103
+ }, [isLoading]);
498104
+ let [userInputOnProcessing, setUserInputOnProcessingRaw] = React175.useState(void 0), userInputBaselineRef = React175.useRef(0), userMessagePendingRef = React175.useRef(!1), loadingStartTimeRef = React175.useRef(0), totalPausedMsRef = React175.useRef(0), pauseStartTimeRef = React175.useRef(null);
497467
498105
  publishSpinnerTimingAnchors({ loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef });
497468
498106
  let resetTimingRefs = React175.useCallback(() => {
497469
498107
  loadingStartTimeRef.current = Date.now(), totalPausedMsRef.current = 0, pauseStartTimeRef.current = null;
@@ -508915,7 +509553,7 @@ function permissionsModule() {
508915
509553
  function filesystemModule() {
508916
509554
  return init_filesystem(), __toCommonJS(filesystem_exports);
508917
509555
  }
508918
- function bashPermissionsModule() {
509556
+ function bashPermissionsModule2() {
508919
509557
  return init_bashPermissions(), __toCommonJS(bashPermissions_exports);
508920
509558
  }
508921
509559
  function bashCommandsModule() {
@@ -508962,15 +509600,15 @@ function denyVerdict(ctx, req) {
508962
509600
  let command8 = args.command;
508963
509601
  if (typeof command8 == "string" && command8.trim() !== "") {
508964
509602
  let segments = bashCommandsModule().splitCommand_DEPRECATED(command8);
508965
- if (segments.length > bashPermissionsModule().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK)
509603
+ if (segments.length > bashPermissionsModule2().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK)
508966
509604
  throw new Error(
508967
- `compound command has ${segments.length} subcommands (cap ${bashPermissionsModule().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK}) \u2014 fail-closed to card`
509605
+ `compound command has ${segments.length} subcommands (cap ${bashPermissionsModule2().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK}) \u2014 fail-closed to card`
508968
509606
  );
508969
509607
  let candidates = segments.length > 1 ? [command8, ...segments] : [command8], candidateEvalFailed = null;
508970
509608
  for (let cand of candidates)
508971
509609
  if (!(typeof cand != "string" || cand.trim() === ""))
508972
509610
  try {
508973
- let result = bashPermissionsModule().bashToolCheckPermission({ command: cand }, ctx);
509611
+ let result = bashPermissionsModule2().bashToolCheckPermission({ command: cand }, ctx);
508974
509612
  if (result.behavior === "deny" && result.decisionReason?.type === "rule") {
508975
509613
  let rule = result.decisionReason.rule;
508976
509614
  return {
@@ -508988,9 +509626,9 @@ function denyVerdict(ctx, req) {
508988
509626
  let command8 = args.command;
508989
509627
  if (typeof command8 == "string" && command8.trim() !== "") {
508990
509628
  let fragments = /[`'"]/.test(command8) ? [] : command8.split(/[;|\n\r{}()&]+/).filter((f) => f.trim() !== "");
508991
- if (fragments.length > bashPermissionsModule().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK)
509629
+ if (fragments.length > bashPermissionsModule2().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK)
508992
509630
  throw new Error(
508993
- `powershell command has ${fragments.length} fragments (cap ${bashPermissionsModule().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK}) \u2014 fail-closed to card`
509631
+ `powershell command has ${fragments.length} fragments (cap ${bashPermissionsModule2().MAX_SUBCOMMANDS_FOR_SECURITY_CHECK}) \u2014 fail-closed to card`
508994
509632
  );
508995
509633
  let candidates = fragments.length > 1 ? [command8, ...fragments] : [command8], candidateEvalFailed = null;
508996
509634
  for (let cand of candidates)
@@ -509033,7 +509671,7 @@ function bashVerdict(ctx, args) {
509033
509671
  let command8 = args.command;
509034
509672
  if (typeof command8 != "string" || command8.trim() === "")
509035
509673
  return { allow: !1, reason: "bash gate carries no command string" };
509036
- let result = bashPermissionsModule().bashToolCheckPermission(
509674
+ let result = bashPermissionsModule2().bashToolCheckPermission(
509037
509675
  { command: command8 },
509038
509676
  ctx
509039
509677
  );
@@ -509273,9 +509911,24 @@ function shellApprovalCardPort(req) {
509273
509911
  // 🔴 两元素**各渲各的**,壳不写互斥逻辑(互斥由 core 铸点保证)。
509274
509912
  ...req.ruleSuggestions !== void 0 && req.ruleSuggestions.length > 0 ? { ruleSuggestions: req.ruleSuggestions } : {},
509275
509913
  ...typeof req.persistedRuleShadowed == "string" && req.persistedRuleShadowed !== "" ? { persistedRuleShadowed: req.persistedRuleShadowed } : {},
509914
+ // ── #249③ / [3892]③ / [3895]:**durable park 腿**的只读候选(F-003 渲染半场)────────────
509915
+ // 修前这一位到卡口就被整段丢掉 ⇒ 那条腿上的 Bash 审批卡恒只有 `1. Yes / 2. No`,CC 的第三态
509916
+ // 在**供给已经在场**(server 7.16.0 起行上就有,client-core 0.29.0 已合形窄化落卡入参)的
509917
+ // 情况下依然渲不出来。
509918
+ // 🔴 与上面的 `ruleSuggestions` **分键不合流**:那一位兑付到 respond 的 `persistRule`,本位
509919
+ // 的 `/decide` 体无规则位 ⇒ 兑付走 `onPersistLocalRuleSelected` 的壳侧 settings 写入,
509920
+ // **决断字节一个键都不多**(常驻套 localRuleSuggestionsCard ⑤-5/⑥-5 钉住)。
509921
+ ...Array.isArray(req.ruleSuggestionsReadOnly) && req.ruleSuggestionsReadOnly.length > 0 ? { ruleSuggestionsReadOnly: req.ruleSuggestionsReadOnly } : {},
509276
509922
  onPersistRuleSelected(rule) {
509277
509923
  selectedPersistRule = rule;
509278
509924
  },
509925
+ // durable 腿的落规则口:写壳自己的 `permissions.allow`(判定与窄化全在 localAllowRuleWrite),
509926
+ // 成败**都**上屏 —— 「点了『不再询问』,下次它又问」是这条腿唯一会被问的问题,静默失败等于
509927
+ // 把答案藏起来。🔴 刻意不碰 `selectedPersistRule`:回决字节与两态时代必须逐字节相同。
509928
+ onPersistLocalRuleSelected(rule) {
509929
+ let outcome = persistLocalAllowRule(rule);
509930
+ surfaceLocalRulePersistOutcome(outcome);
509931
+ },
509279
509932
  // governanceForced(client-core 0.20.1 透传位,server ≥7.5.0):此门来自部署治理层
509280
509933
  // (AUTONOMY/commandPolicy/MANUAL_MODE_SHELL_GATE/SENSITIVE_WRITE_PATTERNS),权限模式
509281
509934
  // 表态掀不掉——呈现走 wireNote 超集车道(dim 行,与 argsOmitted 提示同位,可叠加)。
@@ -509337,6 +509990,8 @@ var asWireTool, forcedToolLoadFailureForTest, forcedToolNilExportForTest, loadFi
509337
509990
  init_appStateRef();
509338
509991
  init_debug();
509339
509992
  init_engineGateSyncAllow();
509993
+ init_localAllowRuleWrite();
509994
+ init_persistedRulesWire();
509340
509995
  init_armedGateRegistry2();
509341
509996
  init_dist();
509342
509997
  asWireTool = (t2) => t2, forcedToolLoadFailureForTest = null, forcedToolNilExportForTest = null, loadFileWrite = () => (init_FileWriteTool(), __toCommonJS(FileWriteTool_exports)).FileWriteTool, loadFileEdit = () => (init_FileEditTool(), __toCommonJS(FileEditTool_exports)).FileEditTool, loadNotebookEdit = () => (init_NotebookEditTool(), __toCommonJS(NotebookEditTool_exports)).NotebookEditTool, loadBash = () => (init_BashTool(), __toCommonJS(BashTool_exports)).BashTool, loadEngineWorkflow = () => (init_EngineWorkflowTool(), __toCommonJS(EngineWorkflowTool_exports)).EngineWorkflowTool, KNOWN_TOOL_LOADERS = {
@@ -509395,27 +510050,6 @@ var init_liveHitlAskWire = __esm({
509395
510050
  }
509396
510051
  });
509397
510052
 
509398
- // build-src/src/sema/approvalDecisionNoteAudit.ts
509399
- function cleanNote(raw2) {
509400
- let flat = raw2.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").replace(/ {2,}/g, " ").trim();
509401
- return flat.length <= 200 ? flat : `${flat.slice(0, 199)}\u2026`;
509402
- }
509403
- function readDecisionNoteAudit(ack) {
509404
- if (ack === null || typeof ack != "object") return { state: "unknown" };
509405
- let o = ack, note = typeof o.decisionNote == "string" && o.decisionNote.trim() !== "" ? cleanNote(o.decisionNote) : void 0;
509406
- return o.noteRecorded === !1 ? { state: "not-recorded", ...note !== void 0 ? { note } : {} } : o.noteRecorded === !0 ? { state: "recorded", ...note !== void 0 ? { note } : {} } : note !== void 0 ? { state: "recorded", note } : { state: "unknown" };
509407
- }
509408
- function decisionNoteAuditLine(audit, opts) {
509409
- if (audit.state === "unknown") return null;
509410
- let quoted = audit.note !== void 0 ? `: "${audit.note}"` : "";
509411
- return opts?.settledElsewhere === !0 ? audit.state === "recorded" ? `this approval was already decided elsewhere \u2014 the reason recorded on the audit trail${quoted}` : `this approval was already decided elsewhere; your reason was not saved to the audit trail \u2014 that decision stands${quoted}` : audit.state === "recorded" ? `decision reason recorded on the audit trail${quoted}` : `your decision stands \u2014 the engine did not save its reason to the audit trail${quoted}`;
509412
- }
509413
- var DECISION_NOTE_NOTICE_KEY, init_approvalDecisionNoteAudit = __esm({
509414
- "build-src/src/sema/approvalDecisionNoteAudit.ts"() {
509415
- DECISION_NOTE_NOTICE_KEY = "approval-decision-note";
509416
- }
509417
- });
509418
-
509419
510053
  // build-src/src/sema/approvalStreamWire.ts
509420
510054
  import {
509421
510055
  isApprovalRequestFrameV1,
@@ -509659,7 +510293,7 @@ async function* consumeStreamApprovalFrames(events3, deps2, opts = {}) {
509659
510293
  release2();
509660
510294
  }
509661
510295
  }
509662
- function abortableDelay3(ms, signal) {
510296
+ function abortableDelay2(ms, signal) {
509663
510297
  return signal.aborted ? Promise.resolve() : new Promise((resolve57) => {
509664
510298
  let t2 = setTimeout(done, ms);
509665
510299
  function done() {
@@ -509685,7 +510319,7 @@ async function* consumeStreamApprovalFrames(events3, deps2, opts = {}) {
509685
510319
  } catch (e) {
509686
510320
  let f = classifyAskDecisionFailure(e);
509687
510321
  if (f.kind === "parking" && attempt < PARKING_POLL_MAX && !signal.aborted) {
509688
- await abortableDelay3(PARKING_POLL_DELAY_MS * (attempt + 1), signal);
510322
+ await abortableDelay2(PARKING_POLL_DELAY_MS * (attempt + 1), signal);
509689
510323
  continue;
509690
510324
  }
509691
510325
  reportDecisionFailure(f, askId), f.kind === "ask-lane-disabled" && await fallbackLegacyRespond(approvalId, decision, askId, signal);
@@ -509775,7 +510409,6 @@ var STREAM_APPROVAL_CAP, ASK_DECISION_DISABLED_CODE, IDEMPOTENCY_KEY_MAX, PARKIN
509775
510409
  "build-src/src/sema/approvalStreamWire.ts"() {
509776
510410
  init_dist();
509777
510411
  init_debug();
509778
- init_approvalDecisionNoteAudit();
509779
510412
  init_appStateRef();
509780
510413
  init_wireNoticePort();
509781
510414
  STREAM_APPROVAL_CAP = "streamApproval", ASK_DECISION_DISABLED_CODE = "feature.approval_ask_disabled", IDEMPOTENCY_KEY_MAX = 255, PARKING_POLL_MAX = 2, PARKING_POLL_DELAY_MS = 400, CAPS_SETTLE_BUDGET_MS = 1500, LEGACY_SETTLE_GRACE_MS = 750, MAX_DECISION_NOTE_CHARS = 2048, TEARDOWN_BUDGET_MS = 1e3;
@@ -509810,7 +510443,7 @@ function classifySubmitFailure(e, signal) {
509810
510443
  let status3 = statusOf2(e);
509811
510444
  return status3 !== void 0 ? status3 === 501 ? "server-verdict" : status3 >= 500 ? "server-5xx" : "server-verdict" : isEngineTransportError(e) || isAbortShaped(e) ? "transport" : "local-error";
509812
510445
  }
509813
- function abortableDelay2(ms, signal) {
510446
+ function abortableDelay(ms, signal) {
509814
510447
  return signal?.aborted === !0 ? Promise.resolve() : new Promise((resolve57) => {
509815
510448
  let t2 = setTimeout(done, ms);
509816
510449
  function done() {
@@ -509844,7 +510477,7 @@ async function submitWithTransportRetry(send, o) {
509844
510477
  if (retriable && attempt < attempts && !overBudget) {
509845
510478
  if (logForDebugging(
509846
510479
  `[sema][wire] approval submit ${o.label}: attempt ${String(attempt)}/${String(attempts)} failed (${kind}) \u2014 retrying in ${String(delay)}ms with the SAME request`
509847
- ), retried = !0, await abortableDelay2(delay, o.signal), o.signal?.aborted === !0)
510480
+ ), retried = !0, await abortableDelay(delay, o.signal), o.signal?.aborted === !0)
509848
510481
  throw logForDebugging(`[sema][wire] approval submit ${o.label}: aborted during backoff \u2014 no further attempt`), e;
509849
510482
  continue;
509850
510483
  }
@@ -528763,7 +529396,7 @@ Usage: sema --remote "your task description"`, () => gracefulShutdown(1));
528763
529396
  pendingHookMessages
528764
529397
  }, renderAndRun);
528765
529398
  }
528766
- }).version("sema 1.0.75", "-v, --version", "Output the version number"), program2.option("-w, --worktree [name]", "Create a new git worktree for this session (optionally specify a name)"), program2.option("--tmux", "Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux."), canUserConfigureAdvisor() && program2.addOption(new Option("--advisor <model>", "Enable the server-side advisor tool with the specified model (alias or full ID).").hideHelp()), program2.addOption(new Option("--bg, --background", "Start the session as a background agent and return immediately (manage with `sema agents`)")), program2.command("ps").description("List background sessions").action(async () => {
529399
+ }).version("sema 1.0.76", "-v, --version", "Output the version number"), program2.option("-w, --worktree [name]", "Create a new git worktree for this session (optionally specify a name)"), program2.option("--tmux", "Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux."), canUserConfigureAdvisor() && program2.addOption(new Option("--advisor <model>", "Enable the server-side advisor tool with the specified model (alias or full ID).").hideHelp()), program2.addOption(new Option("--bg, --background", "Start the session as a background agent and return immediately (manage with `sema agents`)")), program2.command("ps").description("List background sessions").action(async () => {
528767
529400
  await (await Promise.resolve().then(() => (init_bg2(), bg_exports))).psHandler([]), process.exit(process.exitCode ?? 0);
528768
529401
  }), program2.command("logs [id]").description("Print a background session's recent terminal output").action(async (id) => {
528769
529402
  await (await Promise.resolve().then(() => (init_bg2(), bg_exports))).logsHandler(id, []), process.exit(process.exitCode ?? 0);
@@ -531425,7 +532058,8 @@ function seedSemaSettings() {
531425
532058
  }
531426
532059
  try {
531427
532060
  resetUserSettingCache();
531428
- } catch {
532061
+ } catch (e) {
532062
+ failOpen("settings-seed-cache-reset", void 0, String(e));
531429
532063
  }
531430
532064
  return { wrote: !0, path: file2, added };
531431
532065
  }
@@ -531433,6 +532067,7 @@ var SEMA_SETTINGS_DEFAULTS, init_semaSettingsSeed = __esm({
531433
532067
  "build-src/src/sema/mock/semaSettingsSeed.ts"() {
531434
532068
  init_envUtils();
531435
532069
  init_userSetting();
532070
+ init_failOpen();
531436
532071
  SEMA_SETTINGS_DEFAULTS = {
531437
532072
  theme: "dark",
531438
532073
  // LOAD-BEARING: dark → RGB-orange logo + white ⏺ bullet (matches real 187 on a dark terminal)