@sema-agent/cli 1.0.12 → 1.0.14

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@sema-agent/cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@sema-agent/cli",
9
- "version": "1.0.12",
9
+ "version": "1.0.14",
10
10
  "license": "BUSL-1.1",
11
11
  "dependencies": {
12
12
  "@sema-agent/sdk": "0.0.53",
@@ -629,9 +629,9 @@
629
629
  }
630
630
  },
631
631
  "node_modules/@sema-agent/core": {
632
- "version": "1.309.0",
633
- "resolved": "https://registry.npmjs.org/@sema-agent/core/-/core-1.309.0.tgz",
634
- "integrity": "sha512-xn97ll9ia9sdIYHloZCD2IMmMxFyMeEygPoXNpGdD5JNx/owpudZhae1NYQU9NR7yBvnRla7uZz4C841+mLMHw==",
632
+ "version": "1.312.0",
633
+ "resolved": "https://registry.npmjs.org/@sema-agent/core/-/core-1.312.0.tgz",
634
+ "integrity": "sha512-a50syYHBa3TT1FjqZCGwK2YH3gt6o4YiyamJYGwY4ABdwCaj8wGBJZmkQw48J+1XEDo7NL/rV5xtaNzxn656Gw==",
635
635
  "license": "BUSL-1.1",
636
636
  "dependencies": {
637
637
  "@modelcontextprotocol/sdk": "1.29.0",
@@ -645,9 +645,9 @@
645
645
  }
646
646
  },
647
647
  "node_modules/@sema-agent/registry-core": {
648
- "version": "0.10.13",
649
- "resolved": "https://registry.npmjs.org/@sema-agent/registry-core/-/registry-core-0.10.13.tgz",
650
- "integrity": "sha512-3cYQ3eULB9zqMA2+bRW967NEuQSl+Nd0dmGcNvQ2Un0NFmFlx/6PqiZTDndOwS1zZvdGIovRKJgUk9XN6ZmxiQ==",
648
+ "version": "0.10.14",
649
+ "resolved": "https://registry.npmjs.org/@sema-agent/registry-core/-/registry-core-0.10.14.tgz",
650
+ "integrity": "sha512-6EJe0WmrOrnEY/mITs8FHwCbBy5zcUWzRd1QaOYdEPF5H1DGvv/YcowZ4fR9HTWOpa9l8VI5mJv8gJA4EVxD0w==",
651
651
  "license": "BUSL-1.1",
652
652
  "dependencies": {
653
653
  "zod": "^3.24.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Sema — your own Claude Code-grade coding agent, in the terminal.",
5
5
  "license": "BUSL-1.1",
6
6
  "bin": {
@@ -25,10 +25,10 @@
25
25
  "@sema-agent/server": "1.218.0"
26
26
  },
27
27
  "overrides": {
28
- "@sema-agent/core": "1.309.0",
29
- "@sema-agent/registry-core": "0.10.13"
28
+ "@sema-agent/core": "1.312.0",
29
+ "@sema-agent/registry-core": "0.10.14"
30
30
  },
31
- "semaEngineVersion": "server 1.218.0 / core 1.309.0 / registry-core 0.10.13",
31
+ "semaEngineVersion": "server 1.218.0 / core 1.312.0 / registry-core 0.10.14",
32
32
  "engines": {
33
33
  "node": ">=18"
34
34
  }
package/sema.js CHANGED
@@ -107341,6 +107341,8 @@ var init_ink = __esm({
107341
107341
  // screen readers / screen magnifiers track it — so parking at the text
107342
107342
  // input's caret makes CJK input appear inline and lets a11y tools follow.
107343
107343
  cursorDeclaration = null;
107344
+ /** sema 光标/IME:声明光标当前是否已 ?25h 显示(防每帧重复写显隐序)。 */
107345
+ declaredCursorShown = false;
107344
107346
  // Main-screen: physical cursor position after the declared-cursor move,
107345
107347
  // tracked separately from frame.cursor (which must stay at content-bottom
107346
107348
  // for log-update's relative-move invariants). Alt-screen doesn't need
@@ -107627,6 +107629,10 @@ var init_ink = __esm({
107627
107629
  });
107628
107630
  }
107629
107631
  }
107632
+ if (!this.declaredCursorShown) {
107633
+ optimized.push({ type: "stdout", content: "\x1B[?25h" });
107634
+ this.declaredCursorShown = true;
107635
+ }
107630
107636
  this.displayCursor = target;
107631
107637
  } else {
107632
107638
  if (parked !== null && !this.altScreenActive && !hasDiff) {
@@ -107639,6 +107645,10 @@ var init_ink = __esm({
107639
107645
  });
107640
107646
  }
107641
107647
  }
107648
+ if (this.declaredCursorShown) {
107649
+ optimized.push({ type: "stdout", content: "\x1B[?25l" });
107650
+ this.declaredCursorShown = false;
107651
+ }
107642
107652
  this.displayCursor = null;
107643
107653
  }
107644
107654
  }
@@ -359913,6 +359923,15 @@ function genericResultText(r, opts) {
359913
359923
  return typeof bb?.text === "string" ? bb.text : bb?.type === "image" ? "[image]" : "";
359914
359924
  }).filter(Boolean).join("\n");
359915
359925
  }
359926
+ if (s.trimStart().startsWith("[")) {
359927
+ try {
359928
+ const arr = JSON.parse(s);
359929
+ if (Array.isArray(arr) && arr.length > 0 && arr.every((b3) => b3 && typeof b3 === "object" && (b3.type === "text" || b3.type === "image"))) {
359930
+ s = arr.map((bb) => typeof bb.text === "string" ? bb.text : bb.type === "image" ? "[image]" : "").filter(Boolean).join("\n");
359931
+ }
359932
+ } catch {
359933
+ }
359934
+ }
359916
359935
  s = s.trimEnd();
359917
359936
  if (!s) return "";
359918
359937
  if (opts?.full) return s;
@@ -359967,7 +359986,7 @@ function UserToolSuccessMessage({
359967
359986
  if (report) {
359968
359987
  renderedMessage = /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(SubagentReportSummary, { report, isTranscriptMode });
359969
359988
  } else {
359970
- const fallbackText = genericResultText(message.toolUseResult);
359989
+ const fallbackText = genericResultText(message.toolUseResult, { full: Boolean(isTranscriptMode) || verbose });
359971
359990
  if (fallbackText) renderedMessage = /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(ThemedText, { children: fallbackText }) });
359972
359991
  }
359973
359992
  }
@@ -406129,7 +406148,10 @@ var init_lifetimeMarkers = __esm({
406129
406148
 
406130
406149
  // build-src/src/tools/ScheduleCronTool/UI.tsx
406131
406150
  function renderCreateToolUseMessage(input) {
406132
- return `${input.cron ?? ""}${input.prompt ? `: ${truncate(input.prompt, 60, true)}` : ""}`;
406151
+ const s = input.schedule;
406152
+ const sched = input.cron ?? (s?.kind === "cron" && typeof s.expr === "string" ? s.expr : s?.kind === "delay" && typeof s.delaySec === "number" ? `in ${s.delaySec}s` : s?.kind === "at" ? `at ${typeof s.at === "string" ? s.at : "\u2026"}` : void 0);
406153
+ const label = input.label ? ` [${input.label}]` : "";
406154
+ return `${sched ?? ""}${label}${input.prompt ? `: ${truncate(input.prompt, 60, true)}` : ""}`;
406133
406155
  }
406134
406156
  function renderCreateResultMessage(output) {
406135
406157
  return /* @__PURE__ */ (0, import_jsx_runtime154.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime154.jsxs)(ThemedText, { children: [
@@ -406203,10 +406225,17 @@ var init_CronCreateTool = __esm({
406203
406225
  init_UI24();
406204
406226
  MAX_JOBS = 50;
406205
406227
  inputSchema35 = lazySchema(
406206
- () => external_exports.strictObject({
406207
- cron: external_exports.string().describe(
406228
+ () => external_exports.object({
406229
+ cron: external_exports.string().optional().describe(
406208
406230
  'Standard 5-field cron expression in local time: "M H DoM Mon DoW" (e.g. "*/5 * * * *" = every 5 minutes, "30 14 28 2 *" = Feb 28 at 2:30pm local once).'
406209
406231
  ),
406232
+ schedule: external_exports.object({
406233
+ kind: external_exports.string().optional(),
406234
+ expr: external_exports.string().optional(),
406235
+ delaySec: external_exports.number().optional(),
406236
+ at: external_exports.string().optional()
406237
+ }).optional().describe("Engine schedule form (kinds cron/at/delay) \u2014 accepted by the live engine tool face."),
406238
+ label: external_exports.string().optional(),
406210
406239
  prompt: external_exports.string().describe("The prompt to enqueue at each fire time."),
406211
406240
  recurring: semanticBoolean(external_exports.boolean().optional()).describe(
406212
406241
  `true (default) = fire on every cron match until deleted or auto-expired after ${DEFAULT_MAX_AGE_DAYS} days. false = fire once at the next match, then auto-delete. Use false for "remind me at X" one-shot requests with pinned minute/hour/dom/month.`
@@ -406251,6 +406280,13 @@ var init_CronCreateTool = __esm({
406251
406280
  return getCronFilePath();
406252
406281
  },
406253
406282
  async validateInput(input) {
406283
+ if (typeof input.cron !== "string" || input.cron.length === 0) {
406284
+ return {
406285
+ result: false,
406286
+ message: "This local CronCreate accepts the 5-field `cron` string form. The `schedule` object form is handled by the live engine tool \u2014 pass `cron` here.",
406287
+ errorCode: 5
406288
+ };
406289
+ }
406254
406290
  if (!parseCronExpression(input.cron)) {
406255
406291
  return {
406256
406292
  result: false,
@@ -426077,7 +426113,7 @@ var init_BashTool = __esm({
426077
426113
  init_toolName();
426078
426114
  init_UI();
426079
426115
  init_utils6();
426080
- import_jsx_runtime163 = __toESM(require_jsx_runtime());
426116
+ import_jsx_runtime163 = __toESM(require_jsx_runtime(), 1);
426081
426117
  EOL5 = "\n";
426082
426118
  PROGRESS_THRESHOLD_MS3 = 2e3;
426083
426119
  ASSISTANT_BLOCKING_BUDGET_MS2 = 15e3;
@@ -479492,8 +479528,8 @@ var init_sema_brand = __esm({
479492
479528
  "Slash-command gating wired to the fleet capability handshake"
479493
479529
  ]
479494
479530
  },
479495
- productVersion: "1.0.12",
479496
- announcement: "sema 1.0.12 \u2014 background-process and wakeup lifetimes are now honest (tool descriptions say what survives the session; headless wakeup scheduling is refused where it can never fire), finalize no longer triggers early on long-thinking first turns, and scheduler upsert keys stop colliding across sessions"
479531
+ productVersion: "1.0.14",
479532
+ announcement: "sema 1.0.14 \u2014 CJK input method candidate windows now anchor to the input caret (the terminal cursor is shown while typing), and autonomous-loop wakeups resolve to real tick instructions instead of a literal sentinel"
479497
479533
  };
479498
479534
  }
479499
479535
  });
@@ -482427,8 +482463,8 @@ var require_sema_brand = __commonJS({
482427
482463
  "Slash-command gating wired to the fleet capability handshake"
482428
482464
  ]
482429
482465
  },
482430
- productVersion: "1.0.12",
482431
- announcement: "sema 1.0.12 \u2014 background-process and wakeup lifetimes are now honest (tool descriptions say what survives the session; headless wakeup scheduling is refused where it can never fire), finalize no longer triggers early on long-thinking first turns, and scheduler upsert keys stop colliding across sessions"
482466
+ productVersion: "1.0.14",
482467
+ announcement: "sema 1.0.14 \u2014 CJK input method candidate windows now anchor to the input caret (the terminal cursor is shown while typing), and autonomous-loop wakeups resolve to real tick instructions instead of a literal sentinel"
482432
482468
  };
482433
482469
  }
482434
482470
  });
@@ -496964,6 +497000,7 @@ var init_types19 = __esm({
496964
497000
  maxTokens: external_exports3.number().int().positive().optional(),
496965
497001
  autoCompactTokens: external_exports3.number().int().positive().optional(),
496966
497002
  charsPerToken: external_exports3.number().finite().positive().optional(),
497003
+ promptGuidance: external_exports3.array(external_exports3.string()).optional(),
496967
497004
  cost: ModelCost.optional(),
496968
497005
  extraBody: external_exports3.unknown().optional().transform((v2, ctx) => {
496969
497006
  if (v2 === void 0)
@@ -556835,6 +556872,98 @@ var init_fs_write_gate_policy = __esm({
556835
556872
  }
556836
556873
  });
556837
556874
 
556875
+ // node_modules/@sema-agent/core/dist/tools/loop-tick.js
556876
+ function pushNotificationAppendix(persistentVariant) {
556877
+ const r = persistentVariant ? "newly blocked on a decision you won't make alone, you're ending the loop" : "newly blocked on a decision you won't make alone, third straight tick with nothing to do, you're ending the loop";
556878
+ return `
556879
+
556880
+ Use PushNotification when the loop can't move further without the user, or when something landed that they'd want to act on now: ${r}, or a major update arrived (CI went red, a review changes the plan). Progress you made yourself isn't a trigger \u2014 the transcript covers that. One ping per state, not per tick.`;
556881
+ }
556882
+ function cronTick(push) {
556883
+ return `# Autonomous loop tick
556884
+
556885
+ Run the autonomous check using the loop instructions established earlier in this conversation. If you cannot find them, treat this as a no-op tick. The recurring cron will fire the next tick automatically \u2014 do not call ${SCHEDULE_WAKEUP_TOOL_NAME} from this tick.${push}`;
556886
+ }
556887
+ function dynamicTick(push) {
556888
+ return `# Autonomous loop tick (dynamic pacing)
556889
+
556890
+ Run the autonomous check using the loop instructions established earlier in this conversation. If you cannot find them, treat this as a no-op tick.
556891
+
556892
+ You scheduled this tick via the ${SCHEDULE_WAKEUP_TOOL_NAME} tool (not a recurring cron). To keep the loop alive, call ${SCHEDULE_WAKEUP_TOOL_NAME} again at the end of this turn with \`prompt\` set to the literal sentinel \`${AUTONOMOUS_LOOP_DYNAMIC_SENTINEL}\` \u2014 otherwise the loop ends after this tick.${DYNAMIC_APPENDIX}${push}`;
556893
+ }
556894
+ function resolveAutonomousLoopPrompt(prompt, opts) {
556895
+ if (prompt !== AUTONOMOUS_LOOP_SENTINEL && prompt !== AUTONOMOUS_LOOP_DYNAMIC_SENTINEL)
556896
+ return null;
556897
+ const persistent = opts?.persistentVariant === true;
556898
+ const push = opts?.pushNotification === true ? pushNotificationAppendix(persistent) : "";
556899
+ const tick = prompt === AUTONOMOUS_LOOP_DYNAMIC_SENTINEL ? dynamicTick(push) : cronTick(push);
556900
+ if (opts?.firstTick !== true)
556901
+ return tick;
556902
+ const preamble = persistent ? AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT : AUTONOMOUS_LOOP_PREAMBLE;
556903
+ return `${preamble}
556904
+
556905
+ ---
556906
+
556907
+ ${tick}`;
556908
+ }
556909
+ var AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT, DYNAMIC_APPENDIX;
556910
+ var init_loop_tick = __esm({
556911
+ "node_modules/@sema-agent/core/dist/tools/loop-tick.js"() {
556912
+ init_scheduler_tools();
556913
+ AUTONOMOUS_LOOP_PREAMBLE = `# Autonomous loop check
556914
+
556915
+ You're being invoked on a timer while the user is away or occupied. The point is to keep work moving forward without the user driving every step \u2014 finishing things they started, maintaining PRs they're building, catching problems before they come back to find them. You're a steward, not an initiator. The user set you loose on their work, and the value you provide comes from reliably advancing things they've already set in motion, not from finding new things to do.
556916
+
556917
+ The key tension to navigate: the user trusts you enough to run autonomously, but that trust is easily lost. Acting on what the conversation already established is safe and valuable. Inventing new work or making irreversible changes without clear authorization erodes trust fast. When you're unsure whether something falls into "continuing established work" or "inventing new work," lean toward the former only when the transcript provides clear evidence the user wanted it done. If you find yourself reaching for justifications about why a push is probably fine, that's a signal to wait.
556918
+
556919
+ ## What to act on
556920
+
556921
+ The current conversation is your highest-signal source \u2014 re-read the transcript above, since everything there is something the user was actively engaged with. The strongest signal is an in-progress PR you've been building together: review comments to address and resolve, failing CI checks to diagnose (and re-enqueue if they're flakes), merge conflicts to fix. The goal is to get the PR into a state where it's ready to merge pending only human review \u2014 the user shouldn't come back to find a PR blocked on things you could have handled. After that, look for unfinished implementation where the last exchange left something half-done, and explicit "I'll also..." or "next I'll..." commitments the conversation made and didn't honor. Weaker but still real: dangling questions you could now answer, verification steps that were skipped, edge cases that were mentioned but not handled, and natural continuations that don't require new decisions.
556922
+
556923
+ If you find anything in this category, act on it \u2014 actually do the work, don't describe what could be done. Run the tests, don't say "you could run the tests." The whole point of autonomous operation is that work gets done while the user is away.
556924
+
556925
+ When the conversation transcript has nothing left, the current branch's pull/merge request on the user's SCM is the next-best place to look. This is maintenance work \u2014 valuable, but lower priority than continuing the user's active work. Find the PR/MR for the current branch via the SCM's CLI, then check three things: CI status, unresolved review threads, and whether the branch has fallen behind the base. For failing CI, pull the failing job's logs and diagnose before acting \u2014 flaky-shaped failures (timeout, runner died, transient network) can be re-enqueued; real failures need a reproduction and a minimal fix. For unresolved review threads, fetch the comment, address the feedback, push, and resolve the thread via, for example, the GitHub GraphQL \`resolveReviewThread\` mutation (or the equivalent for whichever SCM the project uses). Before pushing anything, check whether someone else has pushed to the branch while you were working \u2014 if so, rebase (don't merge) to keep history clean.
556926
+
556927
+ When CI is green, threads are clear, and there's idle time, sweeping the branch for issues is a good use of that time \u2014 bug-hunt or simplification passes catch problems before reviewers do, saving everyone a round-trip.
556928
+
556929
+ If everything is genuinely quiet \u2014 no conversation work, no PR maintenance \u2014 say so in one sentence and stop. No summary of what you checked, no list of what you might do later. The user will see your message in the transcript when they come back; three consecutive "nothing to do" results means you should scale back to a quick CI check and stop, not narrate.
556930
+
556931
+ ## Repeated invocations
556932
+
556933
+ If you see earlier autonomous checks in this conversation, adjust your scope accordingly. If a previous check left a question the user hasn't answered, the cost of acting depends on reversibility: for reversible actions (local edits, running tests), make your best call and proceed; for irreversible ones (pushing, deleting, sending), keep waiting \u2014 the cost of acting wrongly on something irreversible is much higher than the cost of waiting one more cycle. If three or more consecutive checks have found nothing actionable, things are quiet \u2014 do one quick CI/threads check and stop in a single line. Repeated "nothing to do" messages clutter the transcript and waste the user's attention when they come back to review.
556934
+
556935
+ Read and analyze freely \u2014 understanding the state of things has no blast radius. Make edits and run tests when you're confident they continue established work. Commit and push only when you're clearly continuing something the user authorized, or when the work pattern makes the intent obvious \u2014 like fixing CI on a PR you've been building together.
556936
+ `;
556937
+ AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT = `# Autonomous loop check
556938
+
556939
+ You're being invoked on a timer while the user is away or occupied. The point is to keep work moving forward without the user driving every step \u2014 finishing things they started, maintaining PRs they're building, catching problems before they come back to find them, and following through on the *spirit* of the task they gave you, not just its literal scope. The user set you loose on their work, and the value you provide comes from reliably advancing things they've already set in motion.
556940
+
556941
+ The key tension to navigate: the user trusts you enough to run autonomously, but that trust is easily lost. Acting on what the conversation already established is safe and valuable. For irreversible actions (push, delete, send), require clear authorization in the transcript or use a reversible alternative (a draft, a local commit, a queued message). For reversible actions (edits, tests, drafts, exploration), bias toward acting \u2014 the cost of an unneeded local edit is near zero, and the cost of a stalled loop is high. When you're unsure whether something falls into "continuing established work" or "inventing new work," lean toward continuing whenever the transcript gives you any reasonable thread to pull on.
556942
+
556943
+ ## What to act on
556944
+
556945
+ The current conversation is your highest-signal source \u2014 re-read the transcript above, since everything there is something the user was actively engaged with. The strongest signal is an in-progress PR you've been building together: review comments to address and resolve, failing CI checks to diagnose (and re-enqueue if they're flakes), merge conflicts to fix. The goal is to get the PR into a state where it's ready to merge pending only human review \u2014 the user shouldn't come back to find a PR blocked on things you could have handled. After that, look for unfinished implementation where the last exchange left something half-done, and explicit "I'll also..." or "next I'll..." commitments the conversation made and didn't honor. Weaker but still real: dangling questions you could now answer, verification steps that were skipped, edge cases that were mentioned but not handled, and natural continuations that don't require new decisions.
556946
+
556947
+ If you find anything in this category, act on it \u2014 actually do the work, don't describe what could be done. Run the tests, don't say "you could run the tests." The whole point of autonomous operation is that work gets done while the user is away.
556948
+
556949
+ When the conversation transcript has nothing left, the current branch's pull/merge request on the user's SCM is the next-best place to look. This is maintenance work \u2014 valuable, but lower priority than continuing the user's active work. Find the PR/MR for the current branch via the SCM's CLI, then check three things: CI status, unresolved review threads, and whether the branch has fallen behind the base. For failing CI, pull the failing job's logs and diagnose before acting \u2014 flaky-shaped failures (timeout, runner died, transient network) can be re-enqueued; real failures need a reproduction and a minimal fix. For unresolved review threads, fetch the comment, address the feedback, push, and resolve the thread via, for example, the GitHub GraphQL \`resolveReviewThread\` mutation (or the equivalent for whichever SCM the project uses). Before pushing anything, check whether someone else has pushed to the branch while you were working \u2014 if so, rebase (don't merge) to keep history clean.
556950
+
556951
+ When CI is green, threads are clear, and there's idle time, sweeping the branch for issues is a good use of that time \u2014 bug-hunt or simplification passes catch problems before reviewers do, saving everyone a round-trip.
556952
+
556953
+ If everything is genuinely quiet \u2014 no conversation work, no PR maintenance \u2014 say so in one sentence and keep the loop alive. Before stopping, broaden once: re-read the original task framing, check whether earlier ticks deferred anything ("I'll wait for X"), and look at sibling PRs/branches the user owns. Persistence is the point of autonomous mode. Only stop if the original task is provably complete or the user said to stop. (Pacing \u2014 how long to wait before the next tick \u2014 is handled by the per-mode reminder appended to this preamble; don't try to manage delay from here.)
556954
+
556955
+ ## Repeated invocations
556956
+
556957
+ If you see earlier autonomous checks in this conversation, adjust your scope accordingly. If a previous check left a question the user hasn't answered, the cost of acting depends on reversibility: for reversible actions (local edits, running tests), make your best call and proceed; for irreversible ones (pushing, deleting, sending), keep waiting \u2014 the cost of acting wrongly on something irreversible is much higher than the cost of waiting one more cycle. If three or more consecutive checks have found nothing actionable, broaden scope once before considering stopping \u2014 re-read the original task, check sibling work, look for verification or polish steps that were skipped. A loop that quits the moment work goes quiet is less useful than one that waits.
556958
+
556959
+ Read and analyze freely \u2014 understanding the state of things has no blast radius. Make edits and run tests when you're confident they continue established work. Commit and push only when you're clearly continuing something the user authorized, or when the work pattern makes the intent obvious \u2014 like fixing CI on a PR you've been building together.
556960
+ `;
556961
+ DYNAMIC_APPENDIX = `
556962
+
556963
+ If a Monitor is armed (check TaskList), keep \`delaySeconds\` at 1200\u20131800s \u2014 the Monitor is the wake signal and this is only the fallback heartbeat. If you were woken by a \`<task-notification>\`, handle the event before deciding whether to re-arm. To stop the loop, call ${SCHEDULE_WAKEUP_TOOL_NAME} with \`stop: true\` and TaskStop the monitor (use TaskList to find its task ID if no longer in context).`;
556964
+ }
556965
+ });
556966
+
556838
556967
  // node_modules/@sema-agent/core/dist/core/permission-rules.js
556839
556968
  var init_permission_rules = __esm({
556840
556969
  "node_modules/@sema-agent/core/dist/core/permission-rules.js"() {
@@ -557204,6 +557333,7 @@ var init_dist9 = __esm({
557204
557333
  init_background_shell();
557205
557334
  init_scheduler();
557206
557335
  init_scheduler_tools();
557336
+ init_loop_tick();
557207
557337
  init_tighten_task_spec();
557208
557338
  init_tool_policy();
557209
557339
  init_auto_mode();
@@ -640407,6 +640537,7 @@ async function ensurePrintModeEngine() {
640407
640537
  }
640408
640538
  async function ensureLocalEngineOneShot(label = "one-shot") {
640409
640539
  seedUserSettingsEnv();
640540
+ process.env.SCHEDULER_SESSION_WAKEUP ??= "false";
640410
640541
  try {
640411
640542
  const { applyPersistedTlsTrust: applyPersistedTlsTrust2 } = await Promise.resolve().then(() => (init_tlsTrust(), tlsTrust_exports));
640412
640543
  await applyPersistedTlsTrust2();
@@ -653659,7 +653790,7 @@ Auth: unix socket -R \u2192 local proxy`, "info");
653659
653790
  pendingHookMessages
653660
653791
  }, renderAndRun);
653661
653792
  }
653662
- }).version("sema 1.0.12", "-v, --version", "Output the version number");
653793
+ }).version("sema 1.0.14", "-v, --version", "Output the version number");
653663
653794
  program2.option("-w, --worktree [name]", "Create a new git worktree for this session (optionally specify a name)");
653664
653795
  program2.option("--tmux", "Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux.");
653665
653796
  if (canUserConfigureAdvisor()) {
@@ -657423,19 +657554,29 @@ var init_schedulerDaemon = __esm({
657423
657554
  /** Run one tick: fire due intents, persist the rescheduled/reaped record set. Returns the count fired. */
657424
657555
  async tickOnce() {
657425
657556
  const nowMs = (this.opts.now ?? (() => Date.now()))();
657426
- let all4;
657427
- try {
657428
- all4 = this.store.load();
657429
- } catch (e) {
657430
- this.opts.onError?.(e);
657431
- return 0;
657432
- }
657433
- const owned = [];
657434
- const foreign = [];
657435
- for (const r of all4) (this.opts.owns?.(r) ?? true ? owned : foreign).push(r);
657436
- const { toFire, updated } = tickRecords(owned, nowMs);
657557
+ let toFire = [];
657558
+ const computeNext = (all4) => {
657559
+ const owned = [];
657560
+ const foreign = [];
657561
+ for (const r of all4) (this.opts.owns?.(r) ?? true ? owned : foreign).push(r);
657562
+ const t2 = tickRecords(owned, nowMs);
657563
+ toFire = t2.toFire;
657564
+ if (t2.toFire.length === 0 && t2.updated.length === owned.length && t2.updated.every((r, i) => r === owned[i])) return null;
657565
+ return [...t2.updated, ...foreign];
657566
+ };
657437
657567
  try {
657438
- this.store.save([...updated, ...foreign]);
657568
+ if (this.store.mutate) {
657569
+ const m2 = this.store.mutate(computeNext);
657570
+ if (!m2.ok && m2.reason !== "aborted") {
657571
+ this.opts.onError?.(new Error(`scheduler store mutate failed: ${m2.reason}`));
657572
+ return 0;
657573
+ }
657574
+ if (!m2.ok) return 0;
657575
+ } else {
657576
+ const next = computeNext(this.store.load());
657577
+ if (next === null) return 0;
657578
+ this.store.save(next);
657579
+ }
657439
657580
  } catch (e) {
657440
657581
  this.opts.onError?.(e);
657441
657582
  return 0;
@@ -657529,6 +657670,30 @@ function saveSchedulerStore(path27, records) {
657529
657670
  writeFileSync21(tmp, serializeSchedulerStore(records), { mode: 384 });
657530
657671
  renameSync10(tmp, path27);
657531
657672
  }
657673
+ function mutateSchedulerStore(path27, fn2, opts) {
657674
+ const maxRetries = Math.max(0, opts?.retries ?? 5);
657675
+ const rawOf = () => {
657676
+ try {
657677
+ return readFileSync51(path27, "utf-8");
657678
+ } catch {
657679
+ return null;
657680
+ }
657681
+ };
657682
+ for (let attempt = 0; ; attempt++) {
657683
+ const baseRaw = rawOf();
657684
+ const records = baseRaw === null ? [] : parseSchedulerStore(baseRaw);
657685
+ const next = fn2(records);
657686
+ if (next === null)
657687
+ return { ok: false, reason: "aborted" };
657688
+ if (rawOf() !== baseRaw) {
657689
+ if (attempt >= maxRetries)
657690
+ return { ok: false, reason: "conflict_exhausted", retries: attempt };
657691
+ continue;
657692
+ }
657693
+ saveSchedulerStore(path27, next);
657694
+ return { ok: true, records: next, retries: attempt };
657695
+ }
657696
+ }
657532
657697
  var init_scheduler_store_node = __esm({
657533
657698
  "node_modules/@sema-agent/registry-core/dist/scheduler-store-node.js"() {
657534
657699
  init_scheduler_store();
@@ -657556,8 +657721,18 @@ function effectiveFirePrompt(fire) {
657556
657721
  substituted: true
657557
657722
  };
657558
657723
  }
657724
+ function makeSentinelResolver() {
657725
+ const ticked = /* @__PURE__ */ new Set();
657726
+ return (prompt, recordId) => {
657727
+ const firstTick = !ticked.has(recordId);
657728
+ const resolved = resolveAutonomousLoopPrompt(prompt, { firstTick });
657729
+ if (resolved !== null) ticked.add(recordId);
657730
+ return resolved ?? prompt;
657731
+ };
657732
+ }
657559
657733
  var init_firePrompt = __esm({
657560
657734
  "build-src/src/sema/scheduler/firePrompt.ts"() {
657735
+ init_dist9();
657561
657736
  }
657562
657737
  });
657563
657738
 
@@ -657570,7 +657745,9 @@ function schedulerStorePath() {
657570
657745
  function fileSchedulerStoreIO(storePath) {
657571
657746
  return {
657572
657747
  load: () => loadSchedulerStore(storePath),
657573
- save: (records) => saveSchedulerStore(storePath, records)
657748
+ save: (records) => saveSchedulerStore(storePath, records),
657749
+ // [1003] 唯一跨进程变更原语绑定:daemon tick/sessionReap/孤儿清扫的写路径全走它。
657750
+ mutate: (fn2) => mutateSchedulerStore(storePath, fn2)
657574
657751
  };
657575
657752
  }
657576
657753
  var init_schedulerDaemonWire = __esm({
@@ -657599,6 +657776,15 @@ function partitionSessionReap(records, departedSessionIds) {
657599
657776
  }
657600
657777
  function reapSessionRecords(io, departedSessionIds) {
657601
657778
  if (departedSessionIds.length === 0) return [];
657779
+ if (io.mutate) {
657780
+ let ids = [];
657781
+ const m2 = io.mutate((records) => {
657782
+ const { kept: kept2, reaped: reaped2 } = partitionSessionReap(records, departedSessionIds);
657783
+ ids = reaped2.map((r) => r.id);
657784
+ return reaped2.length === 0 ? null : kept2;
657785
+ });
657786
+ return m2.ok || !m2.ok && m2.reason === "aborted" ? ids : [];
657787
+ }
657602
657788
  const { kept, reaped } = partitionSessionReap(io.load(), departedSessionIds);
657603
657789
  if (reaped.length === 0) return [];
657604
657790
  io.save(kept);
@@ -657677,13 +657863,26 @@ function sweepOrphanSessionRecords(io, storePath, opts = {}) {
657677
657863
  cleanupDead: true
657678
657864
  });
657679
657865
  for (const id of opts.ownSessionIds ?? []) if (id) claimed.add(id);
657680
- const kept = [];
657681
- const reapedIds = [];
657682
- for (const r of records) {
657683
- const orphan = isSessionLifetimeRecord(r) && typeof r.sessionId === "string" && r.sessionId.length > 0 && !claimed.has(r.sessionId) && nowMs - r.createdMs > graceMs;
657684
- if (orphan) reapedIds.push(r.id);
657685
- else kept.push(r);
657686
- }
657866
+ const partition3 = (all4) => {
657867
+ const kept2 = [];
657868
+ const reapedIds2 = [];
657869
+ for (const r of all4) {
657870
+ const orphan = isSessionLifetimeRecord(r) && typeof r.sessionId === "string" && r.sessionId.length > 0 && !claimed.has(r.sessionId) && nowMs - r.createdMs > graceMs;
657871
+ if (orphan) reapedIds2.push(r.id);
657872
+ else kept2.push(r);
657873
+ }
657874
+ return { kept: kept2, reapedIds: reapedIds2 };
657875
+ };
657876
+ if (io.mutate) {
657877
+ let ids = [];
657878
+ const m2 = io.mutate((all4) => {
657879
+ const t2 = partition3(all4);
657880
+ ids = t2.reapedIds;
657881
+ return t2.reapedIds.length === 0 ? null : t2.kept;
657882
+ });
657883
+ return m2.ok || !m2.ok && m2.reason === "aborted" ? ids : [];
657884
+ }
657885
+ const { kept, reapedIds } = partition3(records);
657687
657886
  if (reapedIds.length > 0) io.save(kept);
657688
657887
  return reapedIds;
657689
657888
  }
@@ -657772,8 +657971,10 @@ function wireSchedulerSessionLifecycle(opts = {}) {
657772
657971
  };
657773
657972
  }
657774
657973
  function submitViaQueue(onWarn) {
657974
+ const resolveSentinel = makeSentinelResolver();
657775
657975
  return async (fire) => {
657776
- const { prompt, substituted } = effectiveFirePrompt(fire);
657976
+ const { prompt: rawPrompt, substituted } = effectiveFirePrompt(fire);
657977
+ const prompt = resolveSentinel(rawPrompt, fire.recordId);
657777
657978
  if (substituted) {
657778
657979
  onWarn?.(`scheduler: record ${fire.recordId} fired with an EMPTY prompt \u2014 substituted a placeholder (never inject empty user content)`);
657779
657980
  }