scream-code 0.16.9 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,6 +78,7 @@ const ErrorCodes = {
78
78
  SESSION_FORK_ACTIVE_TURN: "session.fork_active_turn",
79
79
  SESSION_EXPORT_NOT_FOUND: "session.export_not_found",
80
80
  SESSION_EXPORT_MISSING_VERSION: "session.export_missing_version",
81
+ SESSION_DELETE_FAILED: "session.delete_failed",
81
82
  SESSION_CLOSED: "session.closed",
82
83
  SESSION_PERMISSION_MODE_INVALID: "session.permission_mode_invalid",
83
84
  SESSION_THINKING_EMPTY: "session.thinking_empty",
@@ -201,6 +202,12 @@ const SCREAM_ERROR_INFO = {
201
202
  public: true,
202
203
  action: "Provide a version when exporting the session."
203
204
  },
205
+ "session.delete_failed": {
206
+ title: "Failed to delete session",
207
+ retryable: true,
208
+ public: true,
209
+ action: "Close programs holding files in the session directory (editors, dev servers) and retry."
210
+ },
204
211
  "session.closed": {
205
212
  title: "Session is closed",
206
213
  retryable: false,
@@ -46194,15 +46201,15 @@ function catalogProviderModels(entry) {
46194
46201
  /**
46195
46202
  * Idle-stream watchdog.
46196
46203
  *
46197
- * Ported from oh-my-pi `packages/ai/src/utils/idle-iterator.ts:154-382`.
46198
46204
  * Reasoning models (deepseek-reasoner, mimo thinking) can sit silent for
46199
46205
  * 30s+ between tokens; without a watchdog the stream hangs forever and the
46200
46206
  * user thinks the agent died. This wraps any async iterable with a per-item
46201
46207
  * idle deadline, turning a stalled stream into a retryable `APITimeoutError`.
46202
46208
  *
46203
- * Simplified from the upstream version: no `armPreResponseTimeout`, no
46204
- * `iterateWithTerminalGrace`, no per-provider env-var aliases. The core
46205
- * racer-reuse and single-timer-self-rearm design is preserved verbatim.
46209
+ * Deliberately narrow: no pre-response arming, no terminal grace iteration and
46210
+ * no per-provider env-var aliases. The core design — reuse one racer promise
46211
+ * across iterations (re-minted every 1024 races to bound retention) and
46212
+ * self-rearm a single timer per item — is all this needs.
46206
46213
  */
46207
46214
  const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 6e4;
46208
46215
  const DEFAULT_STREAM_FIRST_ITEM_TIMEOUT_MS = 12e4;
@@ -46811,6 +46818,33 @@ function extractError(value) {
46811
46818
  } : { message: value.message };
46812
46819
  }
46813
46820
  //#endregion
46821
+ //#region ../../node_modules/.pnpm/@antfu+utils@9.3.0/node_modules/@antfu/utils/dist/index.mjs
46822
+ function uniq(array) {
46823
+ return Array.from(new Set(array));
46824
+ }
46825
+ function notNullish(v) {
46826
+ return v != null;
46827
+ }
46828
+ function objectMap(obj, fn) {
46829
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => fn(k, v)).filter(notNullish));
46830
+ }
46831
+ function sleep$2(ms, callback) {
46832
+ return new Promise((resolve) => setTimeout(async () => {
46833
+ await callback?.();
46834
+ resolve();
46835
+ }, ms));
46836
+ }
46837
+ function createControlledPromise() {
46838
+ let resolve, reject;
46839
+ const promise = new Promise((_resolve, _reject) => {
46840
+ resolve = _resolve;
46841
+ reject = _reject;
46842
+ });
46843
+ promise.resolve = resolve;
46844
+ promise.reject = reject;
46845
+ return promise;
46846
+ }
46847
+ //#endregion
46814
46848
  //#region ../../packages/agent-core/src/utils/fs.ts
46815
46849
  /**
46816
46850
  * Low-level POSIX durability primitives.
@@ -46911,6 +46945,50 @@ async function atomicWrite(filePath, content, _syncOverride) {
46911
46945
  } catch {}
46912
46946
  }
46913
46947
  }
46948
+ /**
46949
+ * Error codes that indicate a *transient* removal failure: another handle
46950
+ * holds the file or a parent directory right now (Windows keeps files
46951
+ * exclusively locked by editors, indexers, dev servers and virus scanners;
46952
+ * POSIX sees the same from racing deletions). Retrying with a short backoff
46953
+ * turns the common "lock released within a second" case into a success while
46954
+ * a genuinely stuck path still surfaces as an error after the budget.
46955
+ */
46956
+ const RM_RETRY_CODES = new Set([
46957
+ "EBUSY",
46958
+ "EPERM",
46959
+ "ENOTEMPTY",
46960
+ "EMFILE",
46961
+ "EACCES"
46962
+ ]);
46963
+ const RM_RETRY_BACKOFF_MS = [
46964
+ 50,
46965
+ 150,
46966
+ 400,
46967
+ 1e3
46968
+ ];
46969
+ /**
46970
+ * rm -rf with bounded retries on transient OS locks. Throws the last error
46971
+ * when every attempt fails, and never retries codes outside the transient
46972
+ * set. `rmOverride` is a fault-injection seam (same convention as
46973
+ * `atomicWrite`'s `_syncOverride`); production callers must never supply it.
46974
+ */
46975
+ async function rmWithRetry(target, opts = {}) {
46976
+ const rmImpl = opts.rmOverride ?? rm;
46977
+ const backoff = opts.backoffMs ?? RM_RETRY_BACKOFF_MS;
46978
+ const attempts = opts.attempts ?? backoff.length + 1;
46979
+ for (let attempt = 1;; attempt += 1) try {
46980
+ await rmImpl(target, {
46981
+ recursive: true,
46982
+ force: true
46983
+ });
46984
+ return;
46985
+ } catch (error) {
46986
+ const code = error.code;
46987
+ const backoffMs = backoff[attempt - 1];
46988
+ if (attempt >= attempts || backoffMs === void 0 || code === void 0 || !RM_RETRY_CODES.has(code)) throw error;
46989
+ await sleep$2(backoffMs);
46990
+ }
46991
+ }
46914
46992
  //#endregion
46915
46993
  //#region ../../packages/agent-core/src/logging/sinks.ts
46916
46994
  const PENDING_MAX = 1e3;
@@ -47431,33 +47509,6 @@ function ensureScreamHome(homeDir) {
47431
47509
  });
47432
47510
  }
47433
47511
  //#endregion
47434
- //#region ../../node_modules/.pnpm/@antfu+utils@9.3.0/node_modules/@antfu/utils/dist/index.mjs
47435
- function uniq(array) {
47436
- return Array.from(new Set(array));
47437
- }
47438
- function notNullish(v) {
47439
- return v != null;
47440
- }
47441
- function objectMap(obj, fn) {
47442
- return Object.fromEntries(Object.entries(obj).map(([k, v]) => fn(k, v)).filter(notNullish));
47443
- }
47444
- function sleep$2(ms, callback) {
47445
- return new Promise((resolve) => setTimeout(async () => {
47446
- await callback?.();
47447
- resolve();
47448
- }, ms));
47449
- }
47450
- function createControlledPromise() {
47451
- let resolve, reject;
47452
- const promise = new Promise((_resolve, _reject) => {
47453
- resolve = _resolve;
47454
- reject = _reject;
47455
- });
47456
- promise.resolve = resolve;
47457
- promise.reject = reject;
47458
- return promise;
47459
- }
47460
- //#endregion
47461
47512
  //#region ../../packages/agent-core/src/utils/abort.ts
47462
47513
  function abortError() {
47463
47514
  const error = /* @__PURE__ */ new Error("Aborted");
@@ -47524,6 +47575,15 @@ function createMaxStepsExceededError(maxSteps, message) {
47524
47575
  function isMaxStepsExceededError(error) {
47525
47576
  return isScreamError(error) && error.code === ErrorCodes.LOOP_MAX_STEPS_EXCEEDED;
47526
47577
  }
47578
+ /**
47579
+ * Context-window overflow, in both shapes it reaches the loop: the ltod error
47580
+ * class thrown by provider adapters, and the `ScreamError(CONTEXT_OVERFLOW)`
47581
+ * wrap produced by non-ltod adapters. Both need compaction, never a retry, so
47582
+ * retry.ts and the turn-level overflow handler must agree on this predicate.
47583
+ */
47584
+ function isContextOverflowError(error) {
47585
+ return error instanceof APIContextOverflowError || isScreamError(error) && error.code === ErrorCodes.CONTEXT_OVERFLOW;
47586
+ }
47527
47587
  function isAbortError$1(err) {
47528
47588
  if (err instanceof Error) return err.name === "AbortError";
47529
47589
  return false;
@@ -47551,7 +47611,7 @@ async function chatWithRetry(input) {
47551
47611
  for (let attempt = 1;; attempt += 1) try {
47552
47612
  return await input.llm.chat(paramsForAttempt(input, attempt, maxAttempts));
47553
47613
  } catch (error) {
47554
- if (error instanceof APIContextOverflowError) {
47614
+ if (isContextOverflowError(error)) {
47555
47615
  logRequestFailure(input, error, attempt, maxAttempts);
47556
47616
  throw error;
47557
47617
  }
@@ -52335,9 +52395,8 @@ var AgentTool = class {
52335
52395
  * When `timeoutMs` is set and background dispatch is available, the wait is
52336
52396
  * bounded by a race: if the child has not finished by the deadline it is
52337
52397
  * handed to the background task manager (never aborted) and the caller
52338
- * receives a `backgrounded` outcome carrying the task id. This mirrors the
52339
- * reference implementation's foreground-budget → auto-background behaviour:
52340
- * a timeout degrades the wait, it does not destroy the subagent's work.
52398
+ * receives a `backgrounded` outcome carrying the task id. A timeout degrades
52399
+ * the wait; it does not destroy the subagent's work.
52341
52400
  */
52342
52401
  async awaitForegroundCompletion(handle, description, timeoutMs, childController, unlinkChild) {
52343
52402
  if (timeoutMs === void 0 || !this.allowBackground || this.backgroundManager === void 0) return {
@@ -53080,11 +53139,10 @@ function formatTokens$2(tokens) {
53080
53139
  * context on every submission. This wastes context budget and can trap the
53081
53140
  * agent in a loop where it keeps retrying without new information.
53082
53141
  *
53083
- * Inspired by oh-my-pi's `advisor/emission-guard.ts`, but adapted for
53084
- * scream-code's grader model: the grader is called once per `complete`
53085
- * submission (not continuously), so per-round limiting is unnecessary. The
53086
- * guard focuses on normalization, content-free filtering, and exact-text
53087
- * deduplication keyed by goal objective.
53142
+ * Scope is set by this codebase's grader model: the grader is called once per
53143
+ * `complete` submission (not continuously), so per-round limiting is
53144
+ * unnecessary. The guard focuses on normalization, content-free filtering,
53145
+ * and exact-text deduplication keyed by goal objective.
53088
53146
  */
53089
53147
  /** Short phrases that carry no actionable feedback. */
53090
53148
  const CONTENT_FREE_PHRASES = new Set([
@@ -68401,7 +68459,7 @@ const scanCache = new FsScanCache();
68401
68459
  * and, when found, appends a notice to the read result so the model does not
68402
68460
  * silently edit a file with unresolved conflicts.
68403
68461
  *
68404
- * Marker shape is strict (ported from oh-my-pi `conflict-detect.ts:171-176`):
68462
+ * Marker shape is strict:
68405
68463
  * prefix alone, or prefix + single space + label. Lines that merely start
68406
68464
  * with `<` or `=` never match. Only fully-closed blocks (opener + separator
68407
68465
  * + closer all present in the scanned window) are reported — an open block
@@ -84389,6 +84447,10 @@ var AutoModeAskUserQuestionDenyPermissionPolicy = class {
84389
84447
  * unattended in bot mode): reads, in-workspace edits, lookups, planning,
84390
84448
  * coordination requests. Everything outside this allowlist is denied in bot
84391
84449
  * mode (fail-closed) — no ask prompt can reach a human who is not there.
84450
+ *
84451
+ * This list is intentionally separate from `#/tools/tool-catalog`: bot mode
84452
+ * answers "what is reversible unattended", not "what is read-only /
84453
+ * executable", so the two classifications must not be merged.
84392
84454
  */
84393
84455
  const REVERSIBLE_TOOLS = new Set([
84394
84456
  "Read",
@@ -84491,36 +84553,105 @@ var BotModePermissionPolicy = class BotModePermissionPolicy {
84491
84553
  }
84492
84554
  };
84493
84555
  //#endregion
84556
+ //#region ../../packages/agent-core/src/tools/tool-catalog.ts
84557
+ /**
84558
+ * Single catalog of builtin-tool name sets shared by permission and
84559
+ * capability policies. When adding a tool, classify it here once: the
84560
+ * subagent capability filter and the default-approve policy both read from
84561
+ * this file, so no list has to be synced by hand in two places.
84562
+ *
84563
+ * `DEFAULT_AUTO_APPROVE_TOOLS` is copied verbatim from the former per-file
84564
+ * record (including its legacy `SetTodoList` entry) so behavior is
84565
+ * unchanged; the sets are intentionally not re-derived from tool
84566
+ * definitions.
84567
+ */
84568
+ /** Read-only inspection tools (no workspace mutation, no command execution). */
84569
+ const READ_TOOLS = new Set([
84570
+ "AskUserQuestion",
84571
+ "ContactParent",
84572
+ "FetchURL",
84573
+ "Glob",
84574
+ "Grep",
84575
+ "KnowledgeLookup",
84576
+ "LSP",
84577
+ "MemoryLookup",
84578
+ "Read",
84579
+ "ReadGroup",
84580
+ "ReadMediaFile",
84581
+ "ReportFinding",
84582
+ "Skill",
84583
+ "TodoList",
84584
+ "WebSearch"
84585
+ ]);
84586
+ /** Tools that mutate the workspace (files, memory, plans, skills). */
84587
+ const WRITE_TOOLS = new Set([
84588
+ "Edit",
84589
+ "InspectOwnAssets",
84590
+ "MakeSkillApply",
84591
+ "MakeSkillPlan",
84592
+ "ManagePlugin",
84593
+ "MemoryEdit",
84594
+ "MemoryWrite",
84595
+ "Write"
84596
+ ]);
84597
+ /** Tools that execute commands. */
84598
+ const EXECUTE_TOOLS = new Set(["Bash", "python"]);
84599
+ /** Nesting/coordination tools — only `all` mode keeps them. A restricted
84600
+ * child must not be able to spawn an unrestricted grandchild (that would
84601
+ * bypass the tool filtering entirely). WolfPack is batch-spawn sugar over
84602
+ * the same subagent host, so it is filtered too. */
84603
+ const NESTING_TOOLS = new Set([
84604
+ "Agent",
84605
+ "SendSubagentMessage",
84606
+ "WolfPack"
84607
+ ]);
84608
+ /** Goal/session-management tools, always available regardless of mode. */
84609
+ const CORE_TOOLS = new Set([
84610
+ "CreateGoal",
84611
+ "GetGoal",
84612
+ "SetGoalBudget",
84613
+ "UpdateGoal",
84614
+ "WriteGoalNote",
84615
+ "EnterPlanMode",
84616
+ "ExitPlanMode"
84617
+ ]);
84618
+ /**
84619
+ * Tools auto-approved without asking in manual mode, verbatim from the
84620
+ * former default-tool-approve record. `SetTodoList` is a legacy name kept
84621
+ * as-is: no current tool declares it, and removing it would be a behavior
84622
+ * change rather than a refactor.
84623
+ */
84624
+ const DEFAULT_AUTO_APPROVE_TOOLS = new Set([
84625
+ "Read",
84626
+ "Grep",
84627
+ "Glob",
84628
+ "ReadMediaFile",
84629
+ "InspectOwnAssets",
84630
+ "SetTodoList",
84631
+ "TodoList",
84632
+ "TaskList",
84633
+ "TaskOutput",
84634
+ "CronList",
84635
+ "WebSearch",
84636
+ "FetchURL",
84637
+ "Agent",
84638
+ "AskUserQuestion",
84639
+ "Skill",
84640
+ "WolfPack",
84641
+ "CreateGoal",
84642
+ "UpdateGoal",
84643
+ "GetGoal",
84644
+ "SetGoalBudget",
84645
+ "WriteGoalNote",
84646
+ "MakeSkillPlan",
84647
+ "MakeSkillApply"
84648
+ ]);
84649
+ //#endregion
84494
84650
  //#region ../../packages/agent-core/src/agent/permission/policies/default-tool-approve.ts
84495
- const DEFAULT_APPROVE_TOOLS = {
84496
- Read: true,
84497
- Grep: true,
84498
- Glob: true,
84499
- ReadMediaFile: true,
84500
- InspectOwnAssets: true,
84501
- SetTodoList: true,
84502
- TodoList: true,
84503
- TaskList: true,
84504
- TaskOutput: true,
84505
- CronList: true,
84506
- WebSearch: true,
84507
- FetchURL: true,
84508
- Agent: true,
84509
- AskUserQuestion: true,
84510
- Skill: true,
84511
- WolfPack: true,
84512
- CreateGoal: true,
84513
- UpdateGoal: true,
84514
- GetGoal: true,
84515
- SetGoalBudget: true,
84516
- WriteGoalNote: true,
84517
- MakeSkillPlan: true,
84518
- MakeSkillApply: true
84519
- };
84520
84651
  var DefaultToolApprovePermissionPolicy = class {
84521
84652
  name = "default-tool-approve";
84522
84653
  evaluate(context) {
84523
- if (!DEFAULT_APPROVE_TOOLS[context.toolCall.name]) return;
84654
+ if (!DEFAULT_AUTO_APPROVE_TOOLS.has(context.toolCall.name)) return;
84524
84655
  return { kind: "approve" };
84525
84656
  }
84526
84657
  };
@@ -85254,6 +85385,7 @@ var PermissionManager = class {
85254
85385
  let response;
85255
85386
  if (this.agent.rpc?.requestApproval) {
85256
85387
  const approvalId = `approval-${String(++this.nextApprovalId)}`;
85388
+ let timeoutHandle;
85257
85389
  try {
85258
85390
  const customPromise = new Promise((resolve, reject) => {
85259
85391
  this.pendingApprovals.set(approvalId, {
@@ -85276,7 +85408,7 @@ var PermissionManager = class {
85276
85408
  }, { signal }).then(resolve, reject);
85277
85409
  });
85278
85410
  const timeoutPromise = new Promise((_, reject) => {
85279
- setTimeout(() => {
85411
+ timeoutHandle = setTimeout(() => {
85280
85412
  reject(/* @__PURE__ */ new Error(`Approval request timed out after ${String(APPROVAL_TIMEOUT_MS)}ms`));
85281
85413
  }, APPROVAL_TIMEOUT_MS);
85282
85414
  });
@@ -85286,6 +85418,7 @@ var PermissionManager = class {
85286
85418
  const resolved = result.resolveError?.(error);
85287
85419
  return await (resolved === void 0 ? Promise.reject(error) : this.permissionPolicyResolutionToPrepare(resolved, context, policyName));
85288
85420
  } finally {
85421
+ if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
85289
85422
  this.pendingApprovals.delete(approvalId);
85290
85423
  }
85291
85424
  } else response = {
@@ -95189,7 +95322,6 @@ function abortedToolOutput(toolName, signal) {
95189
95322
  if (isUserCancellation(signal.reason)) return `The user manually interrupted "${toolName}" (and anything else running at the same time). This was a deliberate user action, not a system error, timeout, or capacity limit. Do not retry automatically or guess at the cause — wait for the user's next instruction.`;
95190
95323
  return `Tool "${toolName}" was aborted`;
95191
95324
  }
95192
- const executedToolCallBlockIndex = /* @__PURE__ */ new Map();
95193
95325
  /**
95194
95326
  * Record tool calls that arrived in a truncated response (max_tokens,
95195
95327
  * paused, unknown) but were never executed. Each call gets a `tool.call`
@@ -95781,8 +95913,7 @@ function makeErrorToolResult(call, args, output) {
95781
95913
  */
95782
95914
  async function dispatchToolCall(step, call, args, displayFields) {
95783
95915
  const { toolCall, toolName } = call;
95784
- const index = executedToolCallBlockIndex.get(step.stepUuid) ?? 0;
95785
- executedToolCallBlockIndex.set(step.stepUuid, index + 1);
95916
+ const index = step.toolCallBlockIndex.value++;
95786
95917
  await step.dispatchEvent({
95787
95918
  type: "block.start",
95788
95919
  uuid: randomUUID(),
@@ -95850,7 +95981,8 @@ async function executeLoopStep(deps) {
95850
95981
  turnId,
95851
95982
  currentStep,
95852
95983
  stepUuid,
95853
- hasPendingSteer: deps.hasPendingSteer
95984
+ hasPendingSteer: deps.hasPendingSteer,
95985
+ toolCallBlockIndex: { value: 0 }
95854
95986
  };
95855
95987
  await dispatchEvent({
95856
95988
  type: "step.begin",
@@ -102654,7 +102786,7 @@ var TurnFlow = class {
102654
102786
  }
102655
102787
  })).stopReason;
102656
102788
  } catch (error) {
102657
- if (error instanceof APIContextOverflowError || isScreamError(error) && error.code === ErrorCodes.CONTEXT_OVERFLOW) {
102789
+ if (isContextOverflowError(error)) {
102658
102790
  await this.agent.fullCompaction.handleOverflowError(signal, error);
102659
102791
  continue;
102660
102792
  }
@@ -102752,19 +102884,17 @@ function parseEnvBudget(raw) {
102752
102884
  /**
102753
102885
  * Compute the effective `max_completion_tokens` cap.
102754
102886
  *
102755
- * Aligned with oh-my-pi's approach: use a reasonable output-token cap
102756
- * instead of the full context window. `max_context_tokens` is the total
102757
- * (input + output) budget, not the output-only budget. Using it as
102758
- * max_tokens tells the API "generate up to 128K output tokens", which is
102759
- * incorrect - the actual max output is much smaller. The API server
102760
- * clamps it to context_window - input_tokens, which shrinks as context
102761
- * grows, causing "max_tokens limit - no tool call" errors on long
102762
- * sessions.
102887
+ * Use a reasonable output-token cap rather than the full context window:
102888
+ * `max_context_tokens` is the total (input + output) budget, not the
102889
+ * output-only budget. Using it as max_tokens tells the API "generate up to
102890
+ * 128K output tokens", which is incorrect - the actual max output is much
102891
+ * smaller. The API server clamps it to context_window - input_tokens, which
102892
+ * shrinks as context grows, causing "max_tokens limit - no tool call" errors
102893
+ * on long sessions.
102763
102894
  *
102764
- * The 64K cap matches oh-my-pi's OUTPUT_CAP_WHEN_UNKNOWN and is well
102765
- * above any current model's real output limit (typically 8K-32K), so it
102766
- * never artificially limits output while preventing the context-window
102767
- * value from being used directly.
102895
+ * The 64K cap is well above any current model's real output limit (typically
102896
+ * 8K-32K), so it never artificially limits output while preventing the
102897
+ * context-window value from being used directly.
102768
102898
  */
102769
102899
  const OUTPUT_TOKEN_CAP = 64e3;
102770
102900
  function computeCompletionBudgetCap(args) {
@@ -108195,56 +108325,25 @@ function buildSubagentMessage(fromAgentId, toAgentId, operation, text, overrides
108195
108325
  }
108196
108326
  //#endregion
108197
108327
  //#region ../../packages/agent-core/src/session/subagent-capability.ts
108198
- /** Read-only inspection tools (no workspace mutation, no command execution). */
108199
- const READ_TOOLS = new Set([
108200
- "AskUserQuestion",
108201
- "ContactParent",
108202
- "FetchURL",
108203
- "Glob",
108204
- "Grep",
108205
- "KnowledgeLookup",
108206
- "LSP",
108207
- "MemoryLookup",
108208
- "Read",
108209
- "ReadGroup",
108210
- "ReadMediaFile",
108211
- "ReportFinding",
108212
- "Skill",
108213
- "TodoList",
108214
- "WebSearch"
108215
- ]);
108216
- /** Tools that mutate the workspace (files, memory, plans, skills). */
108217
- const WRITE_TOOLS = new Set([
108218
- "Edit",
108219
- "InspectOwnAssets",
108220
- "MakeSkillApply",
108221
- "MakeSkillPlan",
108222
- "ManagePlugin",
108223
- "MemoryEdit",
108224
- "MemoryWrite",
108225
- "Write"
108226
- ]);
108227
- /** Tools that execute commands. */
108228
- const EXECUTE_TOOLS = new Set(["Bash", "python"]);
108229
- /** Nesting/coordination tools — only `all` mode keeps them. A restricted
108230
- * child must not be able to spawn an unrestricted grandchild (that would
108231
- * bypass the tool filtering entirely). WolfPack is batch-spawn sugar over
108232
- * the same subagent host, so it is filtered too. */
108233
- const NESTING_TOOLS = new Set([
108234
- "Agent",
108235
- "SendSubagentMessage",
108236
- "WolfPack"
108237
- ]);
108238
- /** Goal/session-management tools, always available regardless of mode. */
108239
- const CORE_TOOLS = new Set([
108240
- "CreateGoal",
108241
- "GetGoal",
108242
- "SetGoalBudget",
108243
- "UpdateGoal",
108244
- "WriteGoalNote",
108245
- "EnterPlanMode",
108246
- "ExitPlanMode"
108247
- ]);
108328
+ /**
108329
+ * Subagent capability modes and their tool-set policy.
108330
+ *
108331
+ * Capability modes (read-only / read-write / execute / all): a mode is
108332
+ * enforced at the tool level, not just by prompting. `all` keeps the profile's
108333
+ * full tool set; stricter modes strip it down to the tools the mode permits.
108334
+ *
108335
+ * Tool classification is by name; the sets live in `#/tools/tool-catalog`
108336
+ * (shared with the permission policies) so a new tool is classified once.
108337
+ * Unknown tool names are kept (a future tool should fail open rather than
108338
+ * being silently stripped from a read-only child).
108339
+ */
108340
+ const KNOWN_TOOL_SETS = [
108341
+ READ_TOOLS,
108342
+ WRITE_TOOLS,
108343
+ EXECUTE_TOOLS,
108344
+ NESTING_TOOLS,
108345
+ CORE_TOOLS
108346
+ ];
108248
108347
  /**
108249
108348
  * Filter an active tool-name list down to what `mode` permits. `all` returns
108250
108349
  * the input unchanged. Unknown names are preserved.
@@ -108257,13 +108356,6 @@ function filterToolsForCapability(activeTools, mode) {
108257
108356
  return allowed.has(name) || !KNOWN_TOOL_SETS.some((set) => set.has(name));
108258
108357
  });
108259
108358
  }
108260
- const KNOWN_TOOL_SETS = [
108261
- READ_TOOLS,
108262
- WRITE_TOOLS,
108263
- EXECUTE_TOOLS,
108264
- NESTING_TOOLS,
108265
- CORE_TOOLS
108266
- ];
108267
108359
  function union(...sets) {
108268
108360
  const out = /* @__PURE__ */ new Set();
108269
108361
  for (const s of sets) for (const v of s) out.add(v);
@@ -122330,7 +122422,7 @@ function installMuPdfWasmLogger() {
122330
122422
  }
122331
122423
  installMuPdfWasmLogger();
122332
122424
  let markit = async () => {
122333
- const promise = import("./markit-DG5XsAGz.mjs").then(({ Markit }) => {
122425
+ const promise = import("./markit-Dga2W6G6.mjs").then(({ Markit }) => {
122334
122426
  const instance = new Markit();
122335
122427
  markit = () => instance;
122336
122428
  return instance;
@@ -122486,7 +122578,7 @@ function resolveDocumentExtension(url, contentType) {
122486
122578
  }
122487
122579
  } catch {}
122488
122580
  }
122489
- /** Hard ceiling for one markit conversion (omp uses 20s; allow slow hosts). */
122581
+ /** Hard ceiling for one markit conversion — generous enough for slow hosts. */
122490
122582
  const CONVERSION_TIMEOUT_MS = 3e4;
122491
122583
  const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
122492
122584
  const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
@@ -122692,7 +122784,7 @@ var LocalFetchURLProvider = class {
122692
122784
  //#endregion
122693
122785
  //#region ../../packages/agent-core/src/tools/providers/search-timeout.ts
122694
122786
  /**
122695
- * Hard timeout for outbound web-search requests (omp pattern).
122787
+ * Hard timeout for outbound web-search requests.
122696
122788
  *
122697
122789
  * A stalled TCP/TLS connection can keep an undici fetch pending for minutes;
122698
122790
  * composing the caller's signal with a fixed ceiling guarantees the request
@@ -124740,8 +124832,7 @@ async function removeSessionIndexEntry(homeDir, sessionId) {
124740
124832
  if (entry === void 0 || entry.sessionId === sessionId) continue;
124741
124833
  kept.push(line);
124742
124834
  }
124743
- if (kept.length === 0) await writeFile(indexPath, "", "utf-8");
124744
- else await writeFile(indexPath, kept.join("\n") + "\n", "utf-8");
124835
+ await atomicWrite(indexPath, kept.length === 0 ? "" : `${kept.join("\n")}\n`);
124745
124836
  invalidateSessionIndexCache();
124746
124837
  }
124747
124838
  function parseIndexLine(line) {
@@ -124792,18 +124883,6 @@ const SessionSummaryStateSchema = z.object({
124792
124883
  title: z.string().optional(),
124793
124884
  custom: z.record(z.string(), z.unknown()).optional()
124794
124885
  });
124795
- let stateWriteCounter = 0;
124796
- /**
124797
- * Atomic state.json write: plain writeFile truncates the target before the
124798
- * new bytes land, so a process killed mid-write leaves a 0-byte state.json
124799
- * and the next resume crashes on JSON.parse. Temp-file + rename makes the
124800
- * swap all-or-nothing.
124801
- */
124802
- async function writeStateAtomically(statePath, content) {
124803
- const tmpPath = `${statePath}.${process.pid}.${Date.now().toString(36)}.${(stateWriteCounter++).toString(36)}.tmp`;
124804
- await writeFile(tmpPath, content, "utf-8");
124805
- await rename(tmpPath, statePath);
124806
- }
124807
124886
  var SessionStore = class {
124808
124887
  homeDir;
124809
124888
  sessionsDir;
@@ -124823,10 +124902,7 @@ var SessionStore = class {
124823
124902
  workDir
124824
124903
  });
124825
124904
  if (await this.findSessionEntry(input.id) !== void 0) await this.delete(input.id);
124826
- else if (await isDirectory(dir)) await rm(dir, {
124827
- recursive: true,
124828
- force: true
124829
- });
124905
+ else if (await isDirectory(dir)) await rmWithRetry(dir);
124830
124906
  await mkdir(dir, {
124831
124907
  recursive: true,
124832
124908
  mode: 448
@@ -124866,10 +124942,7 @@ var SessionStore = class {
124866
124942
  });
124867
124943
  return summary;
124868
124944
  } catch (error) {
124869
- await rm(targetDir, {
124870
- recursive: true,
124871
- force: true
124872
- }).catch(() => {});
124945
+ await rmWithRetry(targetDir).catch(() => {});
124873
124946
  throw error;
124874
124947
  }
124875
124948
  }
@@ -124893,16 +124966,19 @@ var SessionStore = class {
124893
124966
  title: normalized,
124894
124967
  isCustomTitle: true
124895
124968
  };
124896
- await writeStateAtomically(statePath, `${JSON.stringify(next, null, 2)}\n`);
124969
+ await atomicWrite(statePath, `${JSON.stringify(next, null, 2)}\n`);
124897
124970
  }
124898
124971
  async delete(id) {
124899
124972
  assertSafeSessionId(id);
124900
124973
  const entry = await this.findSessionEntry(id);
124901
- if (entry !== void 0) await rm(entry.sessionDir, {
124902
- recursive: true,
124903
- force: true
124904
- }).catch(() => {});
124905
- await removeSessionIndexEntry(this.homeDir, id).catch(() => {});
124974
+ if (entry !== void 0) {
124975
+ try {
124976
+ await rmWithRetry(entry.sessionDir);
124977
+ } catch (error) {
124978
+ throw new ScreamError(ErrorCodes.SESSION_DELETE_FAILED, `Session "${id}" could not be deleted`, { cause: error });
124979
+ }
124980
+ await removeSessionIndexEntry(this.homeDir, id).catch(() => {});
124981
+ }
124906
124982
  }
124907
124983
  async list(options = {}) {
124908
124984
  const workDir = options.workDir === void 0 ? void 0 : normalizeRequiredWorkDir(options.workDir);
@@ -124998,7 +125074,7 @@ var SessionStore = class {
124998
125074
  agents: rewriteAgentHomedirs(parsed["agents"], sourceDir, targetDir),
124999
125075
  custom: Object.assign({}, isRecord$1(parsed["custom"]) ? parsed["custom"] : {}, input.metadata)
125000
125076
  };
125001
- await writeStateAtomically(statePath, `${JSON.stringify(next, null, 2)}\n`);
125077
+ await atomicWrite(statePath, `${JSON.stringify(next, null, 2)}\n`);
125002
125078
  }
125003
125079
  async summaryFromDir(id, sessionDir, workDir) {
125004
125080
  const dirStat = await stat(sessionDir);
@@ -129882,7 +129958,7 @@ async function handleDiyConfig(host) {
129882
129958
  * Open the full-screen conversation search overlay (same as Ctrl+Shift+F).
129883
129959
  * The overlay is owned by pi-tui; `openSearch` is a TS-private method but a
129884
129960
  * plain instance method at runtime, so we reach it through a cast instead of
129885
- * adding an upstream API for a single caller.
129961
+ * adding a public API to the dependency for a single caller.
129886
129962
  */
129887
129963
  function handleSearchCommand(host) {
129888
129964
  host.state.ui?.openSearch?.();
@@ -137396,7 +137472,6 @@ function renderDiffCode(code, colorFn, highlight, lang, colors) {
137396
137472
  * words with `chalk.inverse()`. Only the first removed/added part has its
137397
137473
  * leading whitespace stripped, so indentation isn't highlighted.
137398
137474
  *
137399
- * Ported from oh-my-pi `packages/coding-agent/src/modes/components/diff.ts:55-95`.
137400
137475
  * Returns the two lines with inline inverse highlighting applied; caller wraps
137401
137476
  * them in the usual add/del gutter + line color.
137402
137477
  */
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-BJSym649.mjs")).main();
9
+ (await import("./app-BL4emsvp.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);