@code-yeongyu/senpi 2026.11.6-1 → 2026.11.6-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/bundle/chunks/{chunk-5MWHZH2O.js → chunk-2FDW2LQL.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-GG2JO3CL.js → chunk-4E7GDG4H.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-K5JLYACP.js → chunk-4GKU755E.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-437VLZQZ.js → chunk-7III44PV.js} +1 -1
  6. package/dist/bundle/chunks/chunk-7PSBD5KS.js +9 -0
  7. package/dist/bundle/chunks/{chunk-KFWHLVVH.js → chunk-C7OKUC7L.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-CX32K5KD.js → chunk-CF5OGBC4.js} +7 -7
  9. package/dist/bundle/chunks/{chunk-7TGZJH6Z.js → chunk-D5MRJWUK.js} +40 -39
  10. package/dist/bundle/chunks/{chunk-E6L5VZSM.js → chunk-DSWDJ7HT.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-6NMTFURP.js → chunk-ECW7VBMB.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-2CSLJLM6.js → chunk-GKAYPJ4E.js} +2 -2
  13. package/dist/bundle/chunks/{chunk-KCGUUIUS.js → chunk-GNSMNSL7.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-V3JSQ3PG.js → chunk-GSKCWXXG.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-USKWGPA6.js → chunk-GTJJ73DE.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-HHSYTDKO.js → chunk-HLM46XEB.js} +2 -2
  17. package/dist/bundle/chunks/{chunk-A4F4J2KH.js → chunk-I3PHVJAS.js} +2 -2
  18. package/dist/bundle/chunks/{chunk-LAMRXFHZ.js → chunk-LNPAT22L.js} +1 -1
  19. package/dist/bundle/chunks/chunk-NH7SYIGN.js +2 -0
  20. package/dist/bundle/chunks/{chunk-ZIIDIVTG.js → chunk-R2Z7CX77.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-32I42CEY.js → chunk-RQ6CHXQ2.js} +1 -1
  22. package/dist/bundle/chunks/chunk-SJI6UB23.js +2 -0
  23. package/dist/bundle/chunks/{chunk-KUIEHAPR.js → chunk-T2SMQPRE.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-IPG434JT.js → chunk-TIPKAMSS.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-RI5ABX7A.js → chunk-TMXRR3N7.js} +2 -2
  26. package/dist/bundle/chunks/chunk-VD7AKTGP.js +3 -0
  27. package/dist/bundle/chunks/{chunk-22JICYKS.js → chunk-WAOGBBZR.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-6MHCFJDB.js → chunk-WGRKM3N6.js} +2 -2
  29. package/dist/bundle/chunks/{chunk-WF5R2VSP.js → chunk-X5ZVYNOL.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-H2FBGZNP.js → chunk-XCMII2VQ.js} +2 -2
  31. package/dist/bundle/chunks/{chunk-5JUCTKYS.js → chunk-YK3ZZCHI.js} +1 -1
  32. package/dist/bundle/chunks/{cli-main-ZQBSX5H5.js → cli-main-T74YJGIF.js} +1 -1
  33. package/dist/bundle/chunks/{hand-entry-7U7354GG.js → hand-entry-AYRUGE2A.js} +2 -2
  34. package/dist/bundle/chunks/{headless-host-7LKTG76U.js → headless-host-ES74ZB6M.js} +1 -1
  35. package/dist/bundle/chunks/{help-fast-path-NLSWH2CA.js → help-fast-path-TMTGLOBG.js} +1 -1
  36. package/dist/bundle/chunks/{host-command-3YRFN35E.js → host-command-YRXZE4TV.js} +1 -1
  37. package/dist/bundle/chunks/{host-lifecycle-6XLVIECJ.js → host-lifecycle-6VCXMPDW.js} +1 -1
  38. package/dist/bundle/chunks/host-lifecycle.js +1 -1
  39. package/dist/bundle/chunks/{host-session-command-F7USXUZV.js → host-session-command-N7IN7ZDV.js} +1 -1
  40. package/dist/bundle/chunks/{interactive-mode-4RF3PUQ2.js → interactive-mode-B5SQDLH7.js} +1 -1
  41. package/dist/bundle/chunks/{list-tips-H6VGDFTS.js → list-tips-DU57HHKA.js} +1 -1
  42. package/dist/bundle/chunks/{main-G2SRZYQN.js → main-UQFVFCNY.js} +1 -1
  43. package/dist/bundle/chunks/{multi-session-host-RPN54GUC.js → multi-session-host-XSUJDHFZ.js} +5 -5
  44. package/dist/bundle/chunks/{openai-codex-responses-MJZKUS6I.js → openai-codex-responses-7V6LTDRU.js} +2 -2
  45. package/dist/bundle/chunks/{package-manager-cli-D2VFD463.js → package-manager-cli-GLUNPQJU.js} +1 -1
  46. package/dist/bundle/chunks/{role-37WVG42T.js → role-RRFFHZF5.js} +1 -1
  47. package/dist/bundle/chunks/{rotation-stream-QELPMM4N.js → rotation-stream-BN2YK2MC.js} +1 -1
  48. package/dist/bundle/chunks/{rpc-mode-P4D3DVYD.js → rpc-mode-JYN3E7P7.js} +1 -1
  49. package/dist/bundle/chunks/rpc-monitor-source-C6VQ3BC4.js +3 -0
  50. package/dist/bundle/chunks/{run-attached-tui-CGA6O5FR.js → run-attached-tui-W236SEOR.js} +1 -1
  51. package/dist/bundle/chunks/{schedule-command-XWSNOAGG.js → schedule-command-B2EJTBOE.js} +1 -1
  52. package/dist/bundle/chunks/session-control-endpoint-YIMLHGLG.js +3 -0
  53. package/dist/bundle/chunks/{session-picker-OA2AUVZG.js → session-picker-E6ZONUJB.js} +1 -1
  54. package/dist/bundle/chunks/session-worker.js +294 -292
  55. package/dist/bundle/cli.js +2 -2
  56. package/dist/bundle/host-supervisor.js +1 -1
  57. package/dist/bundle/index.js +1 -1
  58. package/dist/bundle/rpc-entry.js +1 -1
  59. package/dist/bundle/runtime-manifest.json +1 -1
  60. package/dist/cli/project-trust-headless.d.ts +13 -0
  61. package/dist/cli/project-trust-headless.js +35 -0
  62. package/dist/cli/project-trust.js +13 -50
  63. package/dist/cli/schedule-delivery.d.ts +4 -0
  64. package/dist/cli/schedule-delivery.js +6 -0
  65. package/dist/cli/schedule-runner.js +8 -0
  66. package/dist/cli/session-opening.d.ts +19 -0
  67. package/dist/cli/session-opening.js +47 -1
  68. package/dist/core/agent-session-runtime.d.ts +12 -0
  69. package/dist/core/agent-session-runtime.js +41 -0
  70. package/dist/core/agent-session.js +13 -22
  71. package/dist/core/egress/api-table.d.ts +121 -0
  72. package/dist/core/egress/api-table.js +41 -0
  73. package/dist/core/egress/classify.d.ts +19 -0
  74. package/dist/core/egress/classify.js +197 -0
  75. package/dist/core/egress/credential-fields.d.ts +18 -0
  76. package/dist/core/egress/credential-fields.js +83 -0
  77. package/dist/core/egress/custody-sidecar.d.ts +44 -0
  78. package/dist/core/egress/custody-sidecar.js +91 -0
  79. package/dist/core/egress/device-custody.d.ts +12 -0
  80. package/dist/core/egress/device-custody.js +82 -0
  81. package/dist/core/egress/endpoint.d.ts +33 -0
  82. package/dist/core/egress/endpoint.js +115 -0
  83. package/dist/core/execute-tool-on-host.d.ts +26 -0
  84. package/dist/core/execute-tool-on-host.js +96 -0
  85. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +15 -0
  86. package/dist/core/extensions/builtin/goal/index.js +15 -3
  87. package/dist/core/extensions/builtin/goal/stop-pause.d.ts +2 -0
  88. package/dist/core/extensions/builtin/goal/stop-pause.js +5 -0
  89. package/dist/core/extensions/builtin/herdr/herdr-state.d.ts +10 -0
  90. package/dist/core/extensions/builtin/herdr/herdr-state.js +34 -6
  91. package/dist/core/extensions/builtin/herdr/index.js +6 -2
  92. package/dist/core/extensions/builtin/mcp/service.d.ts +9 -1
  93. package/dist/core/extensions/builtin/mcp/service.js +32 -1
  94. package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.d.ts +43 -0
  95. package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.js +158 -0
  96. package/dist/core/extensions/builtin/terminal/rpc-monitor-render.d.ts +2 -0
  97. package/dist/core/extensions/builtin/terminal/rpc-monitor-render.js +4 -0
  98. package/dist/core/extensions/builtin/terminal/rpc-monitor-source.d.ts +5 -2
  99. package/dist/core/extensions/builtin/terminal/rpc-monitor-source.js +19 -4
  100. package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.d.ts +4 -0
  101. package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.js +15 -0
  102. package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.d.ts +10 -0
  103. package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.js +28 -2
  104. package/dist/core/extensions/builtin/terminal/rpc-remote-source.d.ts +59 -0
  105. package/dist/core/extensions/builtin/terminal/rpc-remote-source.js +15 -0
  106. package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +5 -0
  107. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +14 -1
  108. package/dist/core/extensions/builtin/terminal/tools/monitor-rpc.js +8 -1
  109. package/dist/core/extensions/loader.js +8 -0
  110. package/dist/core/extensions/types.d.ts +6 -0
  111. package/dist/core/foreign-session-holders.d.ts +1 -1
  112. package/dist/core/foreign-session-holders.js +3 -3
  113. package/dist/core/session-import/jsonl-import.d.ts +8 -12
  114. package/dist/core/session-import/jsonl-import.js +99 -40
  115. package/dist/core/session-manager.d.ts +2 -0
  116. package/dist/core/session-manager.js +7 -2
  117. package/dist/core/session-store/durable-binding.d.ts +2 -0
  118. package/dist/core/session-store/durable-binding.js +26 -4
  119. package/dist/core/settings-manager.d.ts +20 -0
  120. package/dist/core/settings-manager.js +51 -0
  121. package/dist/core/trust-manager.d.ts +1 -18
  122. package/dist/core/trust-manager.js +3 -124
  123. package/dist/core/trust-requiring-resources.d.ts +21 -0
  124. package/dist/core/trust-requiring-resources.js +189 -0
  125. package/dist/index.d.ts +1 -0
  126. package/dist/index.js +1 -0
  127. package/dist/main.js +42 -12
  128. package/dist/modes/interactive/interactive-mode.js +2 -2
  129. package/dist/modes/interactive/session-control-commands.js +2 -0
  130. package/dist/modes/rpc/connection-handler.js +17 -0
  131. package/dist/modes/rpc/connection-question-bridge.js +7 -4
  132. package/dist/modes/rpc/effect-commands.d.ts +24 -0
  133. package/dist/modes/rpc/host-boot-extensions.d.ts +4 -0
  134. package/dist/modes/rpc/host-boot-extensions.js +17 -0
  135. package/dist/modes/rpc/host-lifecycle-shutdown.js +11 -2
  136. package/dist/modes/rpc/loopless-runtime-settings.js +5 -1
  137. package/dist/modes/rpc/method-classes-schema-session.d.ts +33 -0
  138. package/dist/modes/rpc/method-classes-schema-session.js +4 -0
  139. package/dist/modes/rpc/method-classes-schema.d.ts +33 -0
  140. package/dist/modes/rpc/method-classes.d.ts +4 -0
  141. package/dist/modes/rpc/method-classes.js +7 -0
  142. package/dist/modes/rpc/multi-session-host.d.ts +2 -0
  143. package/dist/modes/rpc/multi-session-host.js +7 -2
  144. package/dist/modes/rpc/project-workspace-commands.d.ts +60 -0
  145. package/dist/modes/rpc/project-workspace-commands.js +145 -0
  146. package/dist/modes/rpc/rpc-error-types.d.ts +11 -1
  147. package/dist/modes/rpc/rpc-error-types.js +10 -0
  148. package/dist/modes/rpc/rpc-input-validation.js +4 -0
  149. package/dist/modes/rpc/rpc-mutation-response-types.d.ts +13 -0
  150. package/dist/modes/rpc/rpc-read-response-types.d.ts +13 -0
  151. package/dist/modes/rpc/rpc-session-command-types.d.ts +15 -0
  152. package/dist/modes/rpc/session-attachment-gate.js +2 -0
  153. package/dist/modes/rpc/session-command-activity.js +2 -0
  154. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  155. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +41 -2
  156. package/docs/extensions.md +17 -1
  157. package/docs/rpc.md +121 -4
  158. package/docs/terminal-tools.md +8 -0
  159. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  160. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  161. package/package.json +6 -6
  162. package/dist/bundle/chunks/chunk-FUQJK2TU.js +0 -2
  163. package/dist/bundle/chunks/chunk-POZMRM6V.js +0 -2
  164. package/dist/bundle/chunks/chunk-XBG63MJZ.js +0 -8
  165. package/dist/bundle/chunks/chunk-YP47G4YP.js +0 -3
  166. package/dist/bundle/chunks/rpc-monitor-source-KKN2UP4T.js +0 -3
  167. package/dist/bundle/chunks/session-control-endpoint-OST7WAKP.js +0 -3
@@ -78,6 +78,12 @@ export class DurableSessionBinding {
78
78
  this.stale = false;
79
79
  // Bumped by every store commit: a deferred whole-image ledger record is skipped once a later write superseded it.
80
80
  this.generation = 0;
81
+ // Projected appends not yet in the ledger, in file order. A later append's record carries them, so a burst of appends
82
+ // keeps the ledger's prefix chain even though only the last one's record runs.
83
+ this.unrecordedTails = [];
84
+ // A whole-file write whose ledger record a later append superseded: the chain has no start until a record covers the
85
+ // whole file, so the next record writes today's whole image instead of the tails.
86
+ this.wholeUnrecorded = false;
81
87
  this.binding = binding;
82
88
  this.handle = handle;
83
89
  this.importer = importer;
@@ -134,10 +140,20 @@ export class DurableSessionBinding {
134
140
  const offset = this.projectedSize;
135
141
  this.projectedSize += Buffer.byteLength(entry.line) + 1;
136
142
  const sessionId = String(this.handle.header().id);
143
+ this.unrecordedTails.push({ key: entry.key, line: entry.line, offset });
137
144
  this.record(path, async () => {
145
+ const tails = [...this.unrecordedTails];
146
+ const whole = this.wholeUnrecorded ? projectionImage(this.handle.header(), this.handle.entries()) : undefined;
138
147
  const written = await stampOf(path);
139
- if (written !== undefined)
140
- await this.importer.recordProjectedTail(path, sessionId, { ...entry, offset }, written);
148
+ if (written === undefined)
149
+ return;
150
+ if (whole === undefined)
151
+ await this.importer.recordProjectedTail(path, sessionId, tails, written);
152
+ else
153
+ await this.importer.recordProjection(path, whole, written);
154
+ this.unrecordedTails.splice(0, tails.length);
155
+ if (whole !== undefined)
156
+ this.wholeUnrecorded = false;
141
157
  });
142
158
  }
143
159
  /** The caller wrote the whole file from today's serializer: record that image, unless a later write supersedes it. */
@@ -145,10 +161,14 @@ export class DurableSessionBinding {
145
161
  const path = resolve(sessionFile);
146
162
  const contents = projectionImage(this.handle.header(), this.handle.entries());
147
163
  this.projectedSize = Buffer.byteLength(contents);
164
+ this.unrecordedTails = [];
165
+ this.wholeUnrecorded = true;
148
166
  this.record(path, async () => {
149
167
  const written = await stampOf(path);
150
- if (written !== undefined)
151
- await this.importer.recordProjection(path, contents, written);
168
+ if (written === undefined)
169
+ return;
170
+ await this.importer.recordProjection(path, contents, written);
171
+ this.wholeUnrecorded = false;
152
172
  });
153
173
  }
154
174
  /** Marks the projection stale in the store (durable, so a crash before the repair still reprojects on open). */
@@ -198,6 +218,8 @@ export class DurableSessionBinding {
198
218
  this.binding.store.commitSync(this.binding.epoch, [{ op: "delete", key: staleKey(path) }], () => { });
199
219
  this.stale = false;
200
220
  this.projectedSize = Buffer.byteLength(contents);
221
+ this.unrecordedTails = [];
222
+ this.wholeUnrecorded = false;
201
223
  const written = await stampOf(path);
202
224
  if (written !== undefined && generation === this.generation) {
203
225
  await this.importer.recordProjection(path, contents, written);
@@ -199,6 +199,15 @@ export interface SettingsManagerCreateOptions {
199
199
  projectTrusted?: boolean;
200
200
  /** The project trust exists only because the session runs Full access (senpi#3414): leaving it withdraws the trust. */
201
201
  trustFromFullAccess?: boolean;
202
+ /** The launch named the project trust explicitly (`--approve`/`--no-approve`): nothing in the session may change it. */
203
+ projectTrustLocked?: boolean;
204
+ }
205
+ /** The project settings file as stored, read whether or not the project is trusted (reading runs nothing). */
206
+ export interface ProjectSettingsFile {
207
+ path: string;
208
+ exists: boolean;
209
+ settings: Settings | null;
210
+ error: string | null;
202
211
  }
203
212
  export interface SettingsStorage {
204
213
  withLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void;
@@ -293,6 +302,14 @@ export declare class SettingsManager {
293
302
  getSettings(): Settings;
294
303
  getGlobalSettings(): Settings;
295
304
  getProjectSettings(): Settings;
305
+ /** The project settings file at `path` as stored: parsed and migrated like a load, but never gated by trust. */
306
+ readProjectSettingsFile(path: string): ProjectSettingsFile;
307
+ /**
308
+ * Writes one top-level key of the project settings file (`null` removes it) through the same trusted-only,
309
+ * locked, merge-on-write path every project setter uses. Refuses a file that failed to load: that path skips
310
+ * the write silently, which an explicit caller must not mistake for success.
311
+ */
312
+ setProjectSetting(key: string, value: unknown): Promise<void>;
296
313
  getProviderSettings(): Record<string, ProviderConcurrencySettings>;
297
314
  getProviderConcurrencyLimit(providerId: string): number;
298
315
  subscribeToProviderSettings(listener: () => void): () => void;
@@ -322,6 +339,9 @@ export declare class SettingsManager {
322
339
  getTodoFirstTurnPlan(): TodoFirstTurnPlan;
323
340
  getTodoTurnEndBackstop(): boolean;
324
341
  isProjectTrusted(): boolean;
342
+ private projectTrustLocked;
343
+ /** True when the launch named the project trust explicitly; a trust decision saved in the session cannot apply. */
344
+ isProjectTrustLocked(): boolean;
325
345
  /**
326
346
  * True when this session's project trust came only from its Full access preset (never asked, never
327
347
  * saved): leaving Full access withdraws it, and going back restores it (senpi#3414, #3420).
@@ -484,6 +484,7 @@ export class SettingsManager {
484
484
  this.selectedSources = new Map();
485
485
  this.sourceListeners = [];
486
486
  this.providerSettingsListeners = new Set();
487
+ this.projectTrustLocked = false;
487
488
  /**
488
489
  * True when this session's project trust came only from its Full access preset (never asked, never
489
490
  * saved): leaving Full access withdraws it, and going back restores it (senpi#3414, #3420).
@@ -534,6 +535,7 @@ export class SettingsManager {
534
535
  }
535
536
  const manager = new SettingsManager(storage, globalLoad.settings, projectLoad.settings, globalLoad.error, projectLoad.error, initialErrors, projectTrusted, initialSources, settingsPaths);
536
537
  manager.trustFromFullAccess = options.trustFromFullAccess === true;
538
+ manager.projectTrustLocked = options.projectTrustLocked === true;
537
539
  return manager;
538
540
  }
539
541
  /** Create an in-memory SettingsManager (no file I/O) */
@@ -695,6 +697,51 @@ export class SettingsManager {
695
697
  getProjectSettings() {
696
698
  return structuredClone(this.projectSettings);
697
699
  }
700
+ /** The project settings file at `path` as stored: parsed and migrated like a load, but never gated by trust. */
701
+ readProjectSettingsFile(path) {
702
+ let content;
703
+ try {
704
+ this.storage.withLock("project", (current) => {
705
+ content = current;
706
+ return undefined;
707
+ });
708
+ if (content === undefined)
709
+ return { path, exists: false, settings: null, error: null };
710
+ return {
711
+ path,
712
+ exists: true,
713
+ settings: SettingsManager.migrateSettings(parseSettingsJson(content)),
714
+ error: null,
715
+ };
716
+ }
717
+ catch (error) {
718
+ return { path, exists: content !== undefined, settings: null, error: String(error) };
719
+ }
720
+ }
721
+ /**
722
+ * Writes one top-level key of the project settings file (`null` removes it) through the same trusted-only,
723
+ * locked, merge-on-write path every project setter uses. Refuses a file that failed to load: that path skips
724
+ * the write silently, which an explicit caller must not mistake for success.
725
+ */
726
+ async setProjectSetting(key, value) {
727
+ this.assertProjectTrustedForWrite();
728
+ if (this.projectSettingsLoadError) {
729
+ throw new Error(`Project settings file could not be read; refusing to write: ${this.projectSettingsLoadError.message}`);
730
+ }
731
+ const field = key;
732
+ const recordedBefore = new Set(this.errors);
733
+ this.updateProjectSettings(field, (settings) => {
734
+ if (value === null)
735
+ delete settings[field];
736
+ else
737
+ settings[key] = value;
738
+ });
739
+ await this.flush();
740
+ // The queued write records its failure instead of throwing; an explicit caller is told.
741
+ const failure = this.errors.find((entry) => entry.scope === "project" && !recordedBefore.has(entry));
742
+ if (failure)
743
+ throw failure.error;
744
+ }
698
745
  getProviderSettings() {
699
746
  return structuredClone(this.settings.providers ?? {});
700
747
  }
@@ -785,6 +832,10 @@ export class SettingsManager {
785
832
  isProjectTrusted() {
786
833
  return this.projectTrusted;
787
834
  }
835
+ /** True when the launch named the project trust explicitly; a trust decision saved in the session cannot apply. */
836
+ isProjectTrustLocked() {
837
+ return this.projectTrustLocked;
838
+ }
788
839
  isTrustFromFullAccess() {
789
840
  return this.trustFromFullAccess && this.projectTrusted;
790
841
  }
@@ -1,3 +1,4 @@
1
+ export { hasTrustRequiringProjectResources, projectAgentsSkillDirs } from "./trust-requiring-resources.ts";
1
2
  export type ProjectTrustDecision = boolean | null;
2
3
  export interface ProjectTrustStoreEntry {
3
4
  path: string;
@@ -17,24 +18,6 @@ export declare function getProjectTrustParentPath(cwd: string): string | undefin
17
18
  export declare function getProjectTrustOptions(cwd: string, options?: {
18
19
  includeSessionOnly?: boolean;
19
20
  }): ProjectTrustOption[];
20
- /**
21
- * The project `.agents/skills` directories for `cwd`, nearest first: one per folder from cwd up to
22
- * the git root (or the filesystem root), stopping below the user's home. Home and every folder
23
- * above it are never a project; `~/.agents/skills` is the user's own global skills. The ONE walk
24
- * both the trust check and the loader use, over the same spelling and the same git root (#3420
25
- * review): paths stay as spelled (resources are compared as strings), and each folder's real path
26
- * decides whether it is home or above, so a symlink can't carry the walk past home either way.
27
- */
28
- export declare function projectAgentsSkillDirs(cwd: string): string[];
29
- /**
30
- * Returns true when cwd has project-local resources that must be gated by
31
- * project trust: trust-requiring entries under the project config dir or the
32
- * legacy cwd/.pi dir (whose resources are also discovered), or a project
33
- * `.agents/skills` (`projectAgentsSkillDirs`, the same walk the loader uses).
34
- * The user's home and every folder above it are never a project: their config
35
- * dirs and .agents/skills are the user's own global resources (#2465).
36
- */
37
- export declare function hasTrustRequiringProjectResources(cwd: string): boolean;
38
21
  export declare class ProjectTrustStore {
39
22
  private trustPath;
40
23
  constructor(agentDir: string);
@@ -1,70 +1,10 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { readFile, realpath, stat } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
5
4
  import lockfile from "proper-lockfile";
6
- import { CONFIG_DIR_NAME } from "../config.js";
7
- import { canonicalizePath, canonicalizePathStrict, resolvePath } from "../utils/paths.js";
5
+ import { canonicalizePathStrict, resolvePath } from "../utils/paths.js";
8
6
  import { stripBom } from "../utils/text.js";
9
- import { bundledBuiltinExtensions } from "./bundled-resources.js";
10
- const TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES = [
11
- "settings.json",
12
- "mcp.json",
13
- "extensions",
14
- "skills",
15
- "prompts",
16
- "themes",
17
- "SYSTEM.md",
18
- "APPEND_SYSTEM.md",
19
- ];
20
- const LEGACY_PROJECT_CONFIG_DIR_NAME = ".pi";
21
- /**
22
- * A project codemode file that sets any setting naming an executable (the codemode package's
23
- * `executable-settings.json` list, today `languages.pyInterpreter`) makes the session run that executable at start,
24
- * so it asks for a trust decision. A file the check cannot read is treated like a project `mcp.json`, whose mere
25
- * presence asks; so is any file when the list itself cannot be read.
26
- */
27
- function projectCodemodeNamesExecutable(configDir) {
28
- const path = join(configDir, "codemode.json");
29
- if (!existsSync(path))
30
- return false;
31
- const executableSettings = codemodeExecutableSettings();
32
- if (executableSettings === undefined)
33
- return true;
34
- let parsed;
35
- try {
36
- parsed = JSON.parse(stripBom(readFileSync(path, "utf8")));
37
- }
38
- catch {
39
- return true;
40
- }
41
- return executableSettings.some((settingPath) => valueAtPath(parsed, settingPath) !== undefined);
42
- }
43
- function codemodeExecutableSettings() {
44
- const codemode = bundledBuiltinExtensions.find((extension) => extension.id === "codemode");
45
- try {
46
- const packageJson = codemode?.resolvePackage();
47
- if (packageJson === undefined)
48
- return undefined;
49
- const list = JSON.parse(readFileSync(join(dirname(packageJson), "src", "config", "executable-settings.json"), "utf8"));
50
- if (typeof list !== "object" || list === null)
51
- return undefined;
52
- const paths = Reflect.get(list, "projectExecutableSettings");
53
- return Array.isArray(paths) && paths.every((entry) => typeof entry === "string") ? paths : undefined;
54
- }
55
- catch {
56
- return undefined;
57
- }
58
- }
59
- function valueAtPath(value, path) {
60
- let current = value;
61
- for (const key of path.split(".")) {
62
- if (typeof current !== "object" || current === null)
63
- return undefined;
64
- current = Reflect.get(current, key);
65
- }
66
- return current;
67
- }
7
+ export { hasTrustRequiringProjectResources, projectAgentsSkillDirs } from "./trust-requiring-resources.js";
68
8
  function normalizeCwd(cwd) {
69
9
  const resolved = resolvePath(cwd);
70
10
  try {
@@ -202,67 +142,6 @@ function withTrustFileLock(path, fn) {
202
142
  release();
203
143
  }
204
144
  }
205
- /** True when `dir` is the user's home or a folder above it: never a project (#2465). */
206
- function isHomeOrAboveDir(dir, homeDir) {
207
- return dir === homeDir || homeDir.startsWith(dir.endsWith(sep) ? dir : `${dir}${sep}`);
208
- }
209
- /** The nearest folder at or above `dir` that holds `.git`, as spelled; undefined outside a repository. */
210
- function findGitRepoRoot(dir) {
211
- let currentDir = dir;
212
- while (!existsSync(join(currentDir, ".git"))) {
213
- const parentDir = dirname(currentDir);
214
- if (parentDir === currentDir)
215
- return undefined;
216
- currentDir = parentDir;
217
- }
218
- return currentDir;
219
- }
220
- /**
221
- * The project `.agents/skills` directories for `cwd`, nearest first: one per folder from cwd up to
222
- * the git root (or the filesystem root), stopping below the user's home. Home and every folder
223
- * above it are never a project; `~/.agents/skills` is the user's own global skills. The ONE walk
224
- * both the trust check and the loader use, over the same spelling and the same git root (#3420
225
- * review): paths stay as spelled (resources are compared as strings), and each folder's real path
226
- * decides whether it is home or above, so a symlink can't carry the walk past home either way.
227
- */
228
- export function projectAgentsSkillDirs(cwd) {
229
- const homeDir = canonicalizePath(resolvePath(process.env.HOME || homedir()));
230
- const startDir = resolvePath(cwd);
231
- const gitRepoRoot = findGitRepoRoot(startDir);
232
- const dirs = [];
233
- let currentDir = startDir;
234
- while (!isHomeOrAboveDir(canonicalizePath(currentDir), homeDir)) {
235
- dirs.push(join(currentDir, ".agents", "skills"));
236
- if (currentDir === gitRepoRoot)
237
- break;
238
- const parentDir = dirname(currentDir);
239
- if (parentDir === currentDir)
240
- break;
241
- currentDir = parentDir;
242
- }
243
- return dirs;
244
- }
245
- /**
246
- * Returns true when cwd has project-local resources that must be gated by
247
- * project trust: trust-requiring entries under the project config dir or the
248
- * legacy cwd/.pi dir (whose resources are also discovered), or a project
249
- * `.agents/skills` (`projectAgentsSkillDirs`, the same walk the loader uses).
250
- * The user's home and every folder above it are never a project: their config
251
- * dirs and .agents/skills are the user's own global resources (#2465).
252
- */
253
- export function hasTrustRequiringProjectResources(cwd) {
254
- const homeDir = canonicalizePath(resolvePath(process.env.HOME || homedir()));
255
- const currentDir = canonicalizePath(resolvePath(cwd));
256
- if (!isHomeOrAboveDir(currentDir, homeDir)) {
257
- const configDirs = [join(currentDir, CONFIG_DIR_NAME), join(currentDir, LEGACY_PROJECT_CONFIG_DIR_NAME)];
258
- if (configDirs.some((configDir) => TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES.some((entry) => existsSync(join(configDir, entry))))) {
259
- return true;
260
- }
261
- if (projectCodemodeNamesExecutable(join(currentDir, CONFIG_DIR_NAME)))
262
- return true;
263
- }
264
- return projectAgentsSkillDirs(cwd).some((dir) => existsSync(dir));
265
- }
266
145
  export class ProjectTrustStore {
267
146
  constructor(agentDir) {
268
147
  this.trustPath = join(resolvePath(agentDir), "trust.json");
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The project `.agents/skills` directories for `cwd`, nearest first: one per folder from cwd up to
3
+ * the git root (or the filesystem root), stopping below the user's home. Home and every folder
4
+ * above it are never a project; `~/.agents/skills` is the user's own global skills. The ONE walk
5
+ * both the trust check and the loader use, over the same spelling and the same git root (#3420
6
+ * review): paths stay as spelled (resources are compared as strings), and each folder's real path
7
+ * decides whether it is home or above, so a symlink can't carry the walk past home either way.
8
+ */
9
+ export declare function projectAgentsSkillDirs(cwd: string): string[];
10
+ /**
11
+ * Returns true when cwd has project-local resources that must be gated by
12
+ * project trust: trust-requiring entries under the project config dir or the
13
+ * legacy cwd/.pi dir (whose resources are also discovered), or a project
14
+ * `.agents/skills` (`projectAgentsSkillDirs`, the same walk the loader uses).
15
+ * The user's home and every folder above it are never a project: their config
16
+ * dirs and .agents/skills are the user's own global resources (#2465).
17
+ */
18
+ export declare function hasTrustRequiringProjectResources(cwd: string): boolean;
19
+ /** `hasTrustRequiringProjectResources` without blocking: the same walk, answered through `node:fs/promises`. */
20
+ export declare function hasTrustRequiringProjectResourcesAsync(cwd: string): Promise<boolean>;
21
+ //# sourceMappingURL=trust-requiring-resources.d.ts.map
@@ -0,0 +1,189 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { access, readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, sep } from "node:path";
5
+ import { CONFIG_DIR_NAME } from "../config.js";
6
+ import { canonicalizePath, resolvePath } from "../utils/paths.js";
7
+ import { stripBom } from "../utils/text.js";
8
+ import { bundledBuiltinExtensions } from "./bundled-resources.js";
9
+ const TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES = [
10
+ "settings.json",
11
+ "mcp.json",
12
+ "extensions",
13
+ "skills",
14
+ "prompts",
15
+ "themes",
16
+ "SYSTEM.md",
17
+ "APPEND_SYSTEM.md",
18
+ ];
19
+ const LEGACY_PROJECT_CONFIG_DIR_NAME = ".pi";
20
+ function* exists(path) {
21
+ return (yield { kind: "exists", path }) === true;
22
+ }
23
+ function* readText(path) {
24
+ const text = yield { kind: "read", path };
25
+ return typeof text === "string" ? text : undefined;
26
+ }
27
+ function parseJson(text) {
28
+ try {
29
+ return { ok: true, value: JSON.parse(stripBom(text)) };
30
+ }
31
+ catch {
32
+ return { ok: false };
33
+ }
34
+ }
35
+ function valueAtPath(value, path) {
36
+ let current = value;
37
+ for (const key of path.split(".")) {
38
+ if (typeof current !== "object" || current === null)
39
+ return undefined;
40
+ current = Reflect.get(current, key);
41
+ }
42
+ return current;
43
+ }
44
+ function* codemodeExecutableSettings() {
45
+ const codemode = bundledBuiltinExtensions.find((extension) => extension.id === "codemode");
46
+ let packageJson;
47
+ try {
48
+ packageJson = codemode?.resolvePackage();
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ if (packageJson === undefined)
54
+ return undefined;
55
+ const text = yield* readText(join(dirname(packageJson), "src", "config", "executable-settings.json"));
56
+ const list = text === undefined ? { ok: false } : parseJson(text);
57
+ if (!list.ok || typeof list.value !== "object" || list.value === null)
58
+ return undefined;
59
+ const paths = Reflect.get(list.value, "projectExecutableSettings");
60
+ return Array.isArray(paths) && paths.every((entry) => typeof entry === "string") ? paths : undefined;
61
+ }
62
+ /**
63
+ * A project codemode file that sets any setting naming an executable (the codemode package's
64
+ * `executable-settings.json` list, today `languages.pyInterpreter`) makes the session run that executable at start,
65
+ * so it asks for a trust decision. A file the check cannot read is treated like a project `mcp.json`, whose mere
66
+ * presence asks; so is any file when the list itself cannot be read.
67
+ */
68
+ function* projectCodemodeNamesExecutable(configDir) {
69
+ const path = join(configDir, "codemode.json");
70
+ if (!(yield* exists(path)))
71
+ return false;
72
+ const executableSettings = yield* codemodeExecutableSettings();
73
+ if (executableSettings === undefined)
74
+ return true;
75
+ const text = yield* readText(path);
76
+ const parsed = text === undefined ? { ok: false } : parseJson(text);
77
+ if (!parsed.ok)
78
+ return true;
79
+ return executableSettings.some((settingPath) => valueAtPath(parsed.value, settingPath) !== undefined);
80
+ }
81
+ /** True when `dir` is the user's home or a folder above it: never a project (#2465). */
82
+ function isHomeOrAboveDir(dir, homeDir) {
83
+ return dir === homeDir || homeDir.startsWith(dir.endsWith(sep) ? dir : `${dir}${sep}`);
84
+ }
85
+ /** The nearest folder at or above `dir` that holds `.git`, as spelled; undefined outside a repository. */
86
+ function* findGitRepoRoot(dir) {
87
+ let currentDir = dir;
88
+ while (!(yield* exists(join(currentDir, ".git")))) {
89
+ const parentDir = dirname(currentDir);
90
+ if (parentDir === currentDir)
91
+ return undefined;
92
+ currentDir = parentDir;
93
+ }
94
+ return currentDir;
95
+ }
96
+ function homeDirectory() {
97
+ return canonicalizePath(resolvePath(process.env.HOME || homedir()));
98
+ }
99
+ function* agentsSkillDirs(cwd) {
100
+ const homeDir = homeDirectory();
101
+ const startDir = resolvePath(cwd);
102
+ const gitRepoRoot = yield* findGitRepoRoot(startDir);
103
+ const dirs = [];
104
+ let currentDir = startDir;
105
+ while (!isHomeOrAboveDir(canonicalizePath(currentDir), homeDir)) {
106
+ dirs.push(join(currentDir, ".agents", "skills"));
107
+ if (currentDir === gitRepoRoot)
108
+ break;
109
+ const parentDir = dirname(currentDir);
110
+ if (parentDir === currentDir)
111
+ break;
112
+ currentDir = parentDir;
113
+ }
114
+ return dirs;
115
+ }
116
+ function* trustRequiringResources(cwd) {
117
+ const homeDir = homeDirectory();
118
+ const currentDir = canonicalizePath(resolvePath(cwd));
119
+ if (!isHomeOrAboveDir(currentDir, homeDir)) {
120
+ for (const configDir of [join(currentDir, CONFIG_DIR_NAME), join(currentDir, LEGACY_PROJECT_CONFIG_DIR_NAME)]) {
121
+ for (const entry of TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES) {
122
+ if (yield* exists(join(configDir, entry)))
123
+ return true;
124
+ }
125
+ }
126
+ if (yield* projectCodemodeNamesExecutable(join(currentDir, CONFIG_DIR_NAME)))
127
+ return true;
128
+ }
129
+ for (const dir of yield* agentsSkillDirs(cwd)) {
130
+ if (yield* exists(dir))
131
+ return true;
132
+ }
133
+ return false;
134
+ }
135
+ function answerSync(probe) {
136
+ if (probe.kind === "exists")
137
+ return existsSync(probe.path);
138
+ try {
139
+ return readFileSync(probe.path, "utf8");
140
+ }
141
+ catch {
142
+ return undefined;
143
+ }
144
+ }
145
+ async function answerAsync(probe) {
146
+ if (probe.kind === "exists") {
147
+ return access(probe.path).then(() => true, () => false);
148
+ }
149
+ return readFile(probe.path, "utf8").catch(() => undefined);
150
+ }
151
+ function runSync(walk) {
152
+ let step = walk.next();
153
+ while (!step.done)
154
+ step = walk.next(answerSync(step.value));
155
+ return step.value;
156
+ }
157
+ async function runAsync(walk) {
158
+ let step = walk.next();
159
+ while (!step.done)
160
+ step = walk.next(await answerAsync(step.value));
161
+ return step.value;
162
+ }
163
+ /**
164
+ * The project `.agents/skills` directories for `cwd`, nearest first: one per folder from cwd up to
165
+ * the git root (or the filesystem root), stopping below the user's home. Home and every folder
166
+ * above it are never a project; `~/.agents/skills` is the user's own global skills. The ONE walk
167
+ * both the trust check and the loader use, over the same spelling and the same git root (#3420
168
+ * review): paths stay as spelled (resources are compared as strings), and each folder's real path
169
+ * decides whether it is home or above, so a symlink can't carry the walk past home either way.
170
+ */
171
+ export function projectAgentsSkillDirs(cwd) {
172
+ return runSync(agentsSkillDirs(cwd));
173
+ }
174
+ /**
175
+ * Returns true when cwd has project-local resources that must be gated by
176
+ * project trust: trust-requiring entries under the project config dir or the
177
+ * legacy cwd/.pi dir (whose resources are also discovered), or a project
178
+ * `.agents/skills` (`projectAgentsSkillDirs`, the same walk the loader uses).
179
+ * The user's home and every folder above it are never a project: their config
180
+ * dirs and .agents/skills are the user's own global resources (#2465).
181
+ */
182
+ export function hasTrustRequiringProjectResources(cwd) {
183
+ return runSync(trustRequiringResources(cwd));
184
+ }
185
+ /** `hasTrustRequiringProjectResources` without blocking: the same walk, answered through `node:fs/promises`. */
186
+ export function hasTrustRequiringProjectResourcesAsync(cwd) {
187
+ return runAsync(trustRequiringResources(cwd));
188
+ }
189
+ //# sourceMappingURL=trust-requiring-resources.js.map
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ export { createSyntheticSourceInfo } from "./core/source-info.ts";
42
42
  export { type EditDiffResult, generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff.ts";
43
43
  export { type BashOperations, type BashSpawnContext, type BashSpawnHook, type BashToolDetails, type BashToolInput, type BashToolOptions, createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLocalBashOperations, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, type EditOperations, type EditToolDetails, type EditToolInput, type EditToolOptions, type FindOperations, type FindToolDetails, type FindToolInput, type FindToolOptions, formatSize, type GrepOperations, type GrepToolDetails, type GrepToolInput, type GrepToolOptions, type LsOperations, type LsToolDetails, type LsToolInput, type LsToolOptions, type ReadOperations, type ReadToolDetails, type ReadToolInput, type ReadToolOptions, type ToolsOptions, type TruncationOptions, type TruncationResult, truncateHead, truncateLine, truncateTail, type WriteOperations, type WriteToolInput, type WriteToolOptions, withFileMutationQueue, } from "./core/tools/index.ts";
44
44
  export { type CompactReadClassification, classifyRead, type ReadClassifier, registerReadClassifier, } from "./core/tools/read-classifiers.ts";
45
+ export { registerRpcRemoteSource, type RpcRemoteHandlers, type RpcRemoteOpenOptions, type RpcRemoteSettledTurn, type RpcRemoteSource, type RpcRemoteState, type RpcRemoteSubscription, type RpcRemoteTarget, } from "./core/extensions/builtin/terminal/rpc-remote-source.ts";
45
46
  export { hasTrustRequiringProjectResources, type ProjectTrustDecision, ProjectTrustStore, type ProjectTrustStoreEntry, type ProjectTrustUpdate, } from "./core/trust-manager.ts";
46
47
  export { UnknownCommandError, type UnknownCommandReason } from "./core/unknown-command.ts";
47
48
  export { type ModelRoute, type ModelRouteReason, type ModelRouteRequest, VIRTUAL_MODEL_STATE_ENTRY, type VirtualModelDefinition, type VirtualModelStateData, } from "./core/virtual-models.ts";
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ export { generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff
50
50
  // Tools
51
51
  export { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLocalBashOperations, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateLine, truncateTail, withFileMutationQueue, } from "./core/tools/index.js";
52
52
  export { classifyRead, registerReadClassifier, } from "./core/tools/read-classifiers.js";
53
+ export { registerRpcRemoteSource, } from "./core/extensions/builtin/terminal/rpc-remote-source.js";
53
54
  export { hasTrustRequiringProjectResources, ProjectTrustStore, } from "./core/trust-manager.js";
54
55
  export { UnknownCommandError } from "./core/unknown-command.js";
55
56
  export { VIRTUAL_MODEL_STATE_ENTRY, } from "./core/virtual-models.js";