@code-yeongyu/senpi 2026.11.2-3 → 2026.11.3-1

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 (169) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WUPLDYD6.js → anthropic-messages-AFWQB3KN.js} +1 -1
  3. package/dist/bundle/chunks/{args-AOI4NEA6.js → args-O3WS4WCO.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-O4GRBX4R.js → chunk-3WJ2DE2G.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-LMIETUPG.js → chunk-4BF4FLT6.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-T5ZJ2Q4O.js → chunk-4SENPCEA.js} +1 -1
  7. package/dist/bundle/chunks/chunk-5DZD623T.js +448 -0
  8. package/dist/bundle/chunks/{chunk-TXOR5BQL.js → chunk-6BSMXZKQ.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-VRWJJMHO.js → chunk-7565XQAU.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-IWM5QRAE.js → chunk-7KLEFHSU.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-FO56LSUK.js → chunk-BNIZP4OS.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-L3V65XW7.js → chunk-CJWSQ6CK.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-QLTQ3UUI.js → chunk-DAPBNRN5.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-GIY7PGOH.js → chunk-DPBPO5OU.js} +1 -1
  15. package/dist/bundle/chunks/chunk-ESQBKTNG.js +2 -0
  16. package/dist/bundle/chunks/{chunk-N4TSAHRL.js → chunk-EVTTLPIY.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-NYXXKS6D.js → chunk-F4OHTL2K.js} +3 -2
  18. package/dist/bundle/chunks/{chunk-TVMW2SUP.js → chunk-FJ2UCBOA.js} +2 -2
  19. package/dist/bundle/chunks/{chunk-KVPRQ5KN.js → chunk-FLM6GZPT.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-P4AT7SBE.js → chunk-HBCNDC53.js} +1 -1
  21. package/dist/bundle/chunks/chunk-MOH5HOHX.js +2 -0
  22. package/dist/bundle/chunks/{chunk-7FUO655J.js → chunk-MOOFLKLI.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-IHOQR7TU.js → chunk-PX7FF5LE.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-QAM6AXA2.js → chunk-QTQ3ES2S.js} +12 -9
  25. package/dist/bundle/chunks/{chunk-FJSJUVQQ.js → chunk-TOOB6OLG.js} +3 -3
  26. package/dist/bundle/chunks/{chunk-662SCMTH.js → chunk-VPDQ6MOL.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-BW4YXS7X.js → chunk-WCRNJYHO.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-ZSVWKOAJ.js → chunk-ZNLCKKF6.js} +170 -226
  29. package/dist/bundle/chunks/{cli-main-534A3WSC.js → cli-main-XM5FWY3Z.js} +1 -1
  30. package/dist/bundle/chunks/{credential-accounts-OAK2E5O2.js → credential-accounts-YZYSKA7X.js} +1 -1
  31. package/dist/bundle/chunks/{health-write-EB4CPHXN.js → health-write-45CX64T2.js} +1 -1
  32. package/dist/bundle/chunks/{help-fast-path-U2PSNNWO.js → help-fast-path-3EDGRKQM.js} +1 -1
  33. package/dist/bundle/chunks/{host-command-WJIMRBGN.js → host-command-PZ4VYQIW.js} +1 -1
  34. package/dist/bundle/chunks/{host-session-command-PWVPQ2OV.js → host-session-command-OJFARE5Q.js} +1 -1
  35. package/dist/bundle/chunks/{interactive-mode-NPHFDPGH.js → interactive-mode-7JSJVVIS.js} +1 -1
  36. package/dist/bundle/chunks/{login-health-7G2DZVOV.js → login-health-YOPTYRTU.js} +1 -1
  37. package/dist/bundle/chunks/{main-I6FJAFUP.js → main-3YGJ2O4I.js} +1 -1
  38. package/dist/bundle/chunks/{multi-session-host-7E26R5JF.js → multi-session-host-N2RQ7JSZ.js} +4 -4
  39. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  40. package/dist/bundle/chunks/{openai-codex-responses-VNBMMIL3.js → openai-codex-responses-YTWLG4OL.js} +1 -1
  41. package/dist/bundle/chunks/{openai-completions-JLR32APM.js → openai-completions-TIFDYHZL.js} +1 -1
  42. package/dist/bundle/chunks/{openai-images-LEBNERSM.js → openai-images-OAEMY4RG.js} +1 -1
  43. package/dist/bundle/chunks/{openai-responses-JCDUNMHA.js → openai-responses-L35NIG5Y.js} +1 -1
  44. package/dist/bundle/chunks/{package-manager-cli-CBSDV36M.js → package-manager-cli-JPNA27E5.js} +1 -1
  45. package/dist/bundle/chunks/{role-LP6LVNCY.js → role-45Y7PQFC.js} +1 -1
  46. package/dist/bundle/chunks/{rotation-stream-ZDCNHIBE.js → rotation-stream-WXWJKJZN.js} +1 -1
  47. package/dist/bundle/chunks/{rpc-mode-GBBTN2WP.js → rpc-mode-Z7MN2ZWA.js} +1 -1
  48. package/dist/bundle/chunks/{run-attached-tui-PVLUHHJX.js → run-attached-tui-TI6MELEI.js} +1 -1
  49. package/dist/bundle/chunks/{schedule-command-W45NYTTK.js → schedule-command-OZ5ESMIT.js} +1 -1
  50. package/dist/bundle/chunks/{session-control-endpoint-LFLUNALC.js → session-control-endpoint-5YKBYJOO.js} +1 -1
  51. package/dist/bundle/chunks/{session-picker-OZUINLD7.js → session-picker-CMRV72VA.js} +1 -1
  52. package/dist/bundle/chunks/session-worker.js +324 -376
  53. package/dist/bundle/chunks/{state-store-SZARKD36.js → state-store-Q47AYMRI.js} +1 -1
  54. package/dist/bundle/cli.js +2 -2
  55. package/dist/bundle/index.js +1 -1
  56. package/dist/bundle/rpc-entry.js +1 -1
  57. package/dist/bundle/runtime-manifest.json +1 -1
  58. package/dist/core/agent-session.d.ts +8 -0
  59. package/dist/core/agent-session.js +68 -60
  60. package/dist/core/credential-pool/classify.d.ts +3 -2
  61. package/dist/core/credential-pool/classify.js +10 -3
  62. package/dist/core/dynamic-prompt/build.d.ts +2 -1
  63. package/dist/core/dynamic-prompt/build.js +1 -2
  64. package/dist/core/dynamic-prompt/handoff.d.ts +1 -1
  65. package/dist/core/dynamic-prompt/handoff.js +9 -9
  66. package/dist/core/dynamic-prompt/identity.js +1 -1
  67. package/dist/core/dynamic-prompt/intent-gate.d.ts +1 -1
  68. package/dist/core/dynamic-prompt/intent-gate.js +8 -8
  69. package/dist/core/dynamic-prompt/policies.d.ts +1 -1
  70. package/dist/core/dynamic-prompt/policies.js +5 -7
  71. package/dist/core/dynamic-prompt/style.js +5 -5
  72. package/dist/core/dynamic-prompt/tool-section.d.ts +5 -0
  73. package/dist/core/dynamic-prompt/tool-section.js +8 -6
  74. package/dist/core/dynamic-prompt/verification.d.ts +6 -6
  75. package/dist/core/dynamic-prompt/verification.js +12 -12
  76. package/dist/core/dynamic-prompt/working-task.d.ts +1 -1
  77. package/dist/core/dynamic-prompt/working-task.js +3 -3
  78. package/dist/core/dynamic-prompt/workstation.js +1 -1
  79. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.d.ts +13 -0
  80. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js +64 -2
  81. package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +5 -1
  82. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.d.ts +7 -0
  83. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.js +28 -1
  84. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts +2 -0
  85. package/dist/core/extensions/builtin/bash-timeout/index.d.ts +1 -1
  86. package/dist/core/extensions/builtin/bash-timeout/index.js +3 -28
  87. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +0 -11
  88. package/dist/core/extensions/builtin/bash-timeout/timeout.js +0 -11
  89. package/dist/core/extensions/builtin/imagegen/index.d.ts +0 -1
  90. package/dist/core/extensions/builtin/imagegen/index.js +0 -11
  91. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts +5 -5
  92. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js +5 -5
  93. package/dist/core/extensions/builtin/prompt-preset/glm-5.d.ts +1 -1
  94. package/dist/core/extensions/builtin/prompt-preset/glm-5.js +1 -1
  95. package/dist/core/extensions/builtin/terminal/extension.js +8 -1
  96. package/dist/core/extensions/builtin/terminal/notify.js +9 -3
  97. package/dist/core/extensions/builtin/terminal/prompt.d.ts +7 -1
  98. package/dist/core/extensions/builtin/terminal/prompt.js +27 -60
  99. package/dist/core/extensions/builtin/terminal/tools/bash.js +10 -2
  100. package/dist/core/extensions/builtin/todotools/normalize.d.ts +20 -3
  101. package/dist/core/extensions/builtin/todotools/normalize.js +251 -5
  102. package/dist/core/extensions/builtin/todotools/prompt.d.ts +2 -2
  103. package/dist/core/extensions/builtin/todotools/prompt.js +5 -5
  104. package/dist/core/extensions/builtin/todotools/tools/todo.js +9 -8
  105. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +0 -5
  106. package/dist/core/extensions/builtin/websearch/index.js +9 -4
  107. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts +2 -0
  108. package/dist/core/extensions/builtin/websearch/websearch/tool.js +3 -1
  109. package/dist/core/model-runtime.js +1 -1
  110. package/dist/core/provider-failure.d.ts +6 -0
  111. package/dist/core/provider-failure.js +6 -2
  112. package/dist/core/retry-fallback/exhaustion.d.ts +18 -0
  113. package/dist/core/retry-fallback/exhaustion.js +20 -0
  114. package/dist/core/system-prompt.js +1 -1
  115. package/dist/core/tools/bash.d.ts +3 -1
  116. package/dist/core/tools/bash.js +14 -4
  117. package/dist/core/tools/edit.d.ts +1 -1
  118. package/dist/core/tools/edit.js +1 -6
  119. package/dist/core/tools/grep/index.js +2 -3
  120. package/dist/core/tools/powershell.d.ts +1 -1
  121. package/dist/core/tools/powershell.js +1 -1
  122. package/dist/core/tools/read.d.ts +1 -1
  123. package/dist/core/tools/read.js +1 -1
  124. package/dist/core/tools/write.d.ts +1 -1
  125. package/dist/core/tools/write.js +1 -1
  126. package/dist/engine-trust/shell-trust.d.ts +36 -0
  127. package/dist/engine-trust/shell-trust.js +44 -0
  128. package/dist/engine-trust/transport-failure.d.ts +12 -0
  129. package/dist/engine-trust/transport-failure.js +48 -0
  130. package/dist/modes/interactive/interactive-mode.js +9 -3
  131. package/dist/modes/print-mode.js +2 -1
  132. package/dist/modes/rpc/compact-catalog-families.d.ts +5 -2
  133. package/dist/modes/rpc/compact-catalog-families.js +5 -2
  134. package/dist/modes/rpc/compact-catalog-registry.d.ts +9 -1
  135. package/dist/modes/rpc/compact-catalog-registry.js +9 -2
  136. package/dist/modes/rpc/compact-recovery-manifest-kinds.d.ts +17 -0
  137. package/dist/modes/rpc/compact-recovery-manifest-kinds.js +21 -0
  138. package/dist/modes/rpc/compact-recovery-manifest-shape.d.ts +7 -0
  139. package/dist/modes/rpc/compact-recovery-manifest.js +11 -3
  140. package/dist/modes/rpc/compact-socket-commands.js +6 -0
  141. package/dist/modes/rpc/connection-handler.js +21 -5
  142. package/dist/modes/rpc/connection-question-bridge.d.ts +3 -1
  143. package/dist/modes/rpc/connection-question-bridge.js +4 -1
  144. package/dist/modes/rpc/connection-roles.d.ts +8 -0
  145. package/dist/modes/rpc/connection-roles.js +18 -0
  146. package/dist/modes/rpc/method-classes-schema-session.d.ts +4 -0
  147. package/dist/modes/rpc/method-classes-schema-session.js +1 -0
  148. package/dist/modes/rpc/method-classes-schema.d.ts +4 -0
  149. package/dist/modes/rpc/protocol-identity.js +2 -0
  150. package/dist/modes/rpc/remote-origin.d.ts +31 -0
  151. package/dist/modes/rpc/remote-origin.js +58 -0
  152. package/dist/modes/rpc/rpc-protocol-info-types.d.ts +8 -0
  153. package/dist/modes/rpc/rpc-question-types.d.ts +7 -0
  154. package/dist/modes/rpc/rpc-question-types.js +0 -3
  155. package/dist/modes/rpc/rpc-session-command-types.d.ts +3 -0
  156. package/dist/modes/rpc/session-registry.js +12 -3
  157. package/dist/modes/rpc/session-worker-client.js +2 -0
  158. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -0
  159. package/dist/modes/rpc/session-worker.js +2 -0
  160. package/docs/engine-socket/fixtures/admission-routes/c-baseline.json +3 -3
  161. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  162. package/docs/engine-socket/fixtures/admission-routes/i-reset-stream.json +3 -3
  163. package/docs/engine-socket/fixtures/recovery-manifest/index.json +32 -0
  164. package/docs/engine-socket/fixtures/recovery-manifest/one-partial-family.json +118 -0
  165. package/docs/rpc.md +93 -3
  166. package/package.json +6 -6
  167. package/dist/bundle/chunks/chunk-7UC4W2T2.js +0 -2
  168. package/dist/bundle/chunks/chunk-R23WXN4L.js +0 -448
  169. package/dist/bundle/chunks/chunk-W5NLCRB3.js +0 -2
@@ -30,9 +30,8 @@ const grepSchema = Type.Object({
30
30
  });
31
31
  export const grepToolSystemPromptContribution = {
32
32
  snippet: "Search file contents (regex/literal) across paths with structured results; respects .gitignore",
33
- guidelines: [
34
- "Use tool.grep for content search instead of rg/grep in a shell; paginate with skip and narrow with glob/path when a page is full",
35
- ],
33
+ // The eval-only grep guideline (system-prompt.ts getEvalOnlyGrepGuideline) carries the pagination rule once.
34
+ guidelines: [],
36
35
  };
37
36
  async function pathStat(path) {
38
37
  try {
@@ -1,7 +1,7 @@
1
1
  import { type BashOperations, type BashSpawnContext, type BashSpawnHook, type BashToolDetails, type BashToolInput, type BashToolOptions, type createBashTool, createShellToolDefinition } from "./bash.ts";
2
2
  export declare const powershellToolSystemPromptContribution: {
3
3
  readonly snippet: "Execute PowerShell commands";
4
- readonly guidelines: readonly ["You can inspect PI_* environment variables for current model and session details."];
4
+ readonly guidelines: readonly ["PI_* env vars = current model + session details."];
5
5
  };
6
6
  export type PowerShellOperations = BashOperations;
7
7
  export type PowerShellSpawnContext = BashSpawnContext;
@@ -4,7 +4,7 @@ import { wrapToolDefinition } from "./tool-definition-wrapper.js";
4
4
  const UTF8_OUTPUT_PREFIX = "try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}\n";
5
5
  export const powershellToolSystemPromptContribution = {
6
6
  snippet: "Execute PowerShell commands",
7
- guidelines: ["You can inspect PI_* environment variables for current model and session details."],
7
+ guidelines: ["PI_* env vars = current model + session details."],
8
8
  };
9
9
  export function createLocalPowerShellOperations() {
10
10
  const operations = createLocalShellOperations("PowerShell", getPowerShellConfig);
@@ -11,7 +11,7 @@ declare const readSchema: Type.TObject<{
11
11
  }>;
12
12
  export declare const readToolSystemPromptContribution: {
13
13
  readonly snippet: "Read file contents";
14
- readonly guidelines: readonly ["Use read to examine files instead of cat or sed."];
14
+ readonly guidelines: readonly ["Files: read, not cat/sed."];
15
15
  };
16
16
  export type ReadToolInput = Static<typeof readSchema>;
17
17
  export interface ReadToolDetails {
@@ -17,7 +17,7 @@ const readSchema = Type.Object({
17
17
  });
18
18
  export const readToolSystemPromptContribution = {
19
19
  snippet: "Read file contents",
20
- guidelines: ["Use read to examine files instead of cat or sed."],
20
+ guidelines: ["Files: read, not cat/sed."],
21
21
  };
22
22
  const LOCAL_URI_SCHEME = /^local:\/\//i;
23
23
  const LOCAL_URI_GUIDANCE = "local:// URIs resolve only inside eval cells via the kernel read()/write() helpers; the read tool takes filesystem paths. Re-read this with the eval read() helper, or retry with the plain absolute file path.";
@@ -8,7 +8,7 @@ declare const writeSchema: Type.TObject<{
8
8
  }>;
9
9
  export declare const writeToolSystemPromptContribution: {
10
10
  readonly snippet: "Create or overwrite files";
11
- readonly guidelines: readonly ["Use write only for new files or complete rewrites."];
11
+ readonly guidelines: readonly ["write only for new files or full rewrites."];
12
12
  };
13
13
  export type WriteToolInput = Static<typeof writeSchema>;
14
14
  /**
@@ -13,7 +13,7 @@ const writeSchema = Type.Object({
13
13
  });
14
14
  export const writeToolSystemPromptContribution = {
15
15
  snippet: "Create or overwrite files",
16
- guidelines: ["Use write only for new files or complete rewrites."],
16
+ guidelines: ["write only for new files or full rewrites."],
17
17
  };
18
18
  const defaultWriteOperations = {
19
19
  writeFile: (path, content) => fsWriteFile(path, content, "utf-8"),
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The trust annotation on agent-shell results (omo#10762, design r13 6.7). Under a fallback generation a
3
+ * command that exits non-zero carries a trusted status taken from the adopted record and, only when its
4
+ * output looks like a certificate verification error, an untrusted hint naming the matched catalog pattern.
5
+ * The output never supplies a reason or an action: any command can print any text, including a forged
6
+ * Bun footer or trust line (design M16). Like `adopted.ts`, this module does not load the marker parser.
7
+ */
8
+ import { type EngineTrustActionId } from "./actions.ts";
9
+ import type { ENGINE_TRUST_FALLBACK_REASONS } from "./marker.ts";
10
+ /** Catalog order decides which entry a hint names when several match. */
11
+ declare const SHELL_TRUST_PATTERNS: readonly [readonly ["UNABLE_TO_GET_ISSUER_CERT_LOCALLY", RegExp], readonly ["UNABLE_TO_GET_ISSUER_CERT", RegExp], readonly ["UNABLE_TO_VERIFY_LEAF_SIGNATURE", RegExp], readonly ["SELF_SIGNED_CERT_IN_CHAIN", RegExp], readonly ["DEPTH_ZERO_SELF_SIGNED_CERT", RegExp], readonly ["CERT_UNTRUSTED", RegExp], readonly ["unable-to-verify-the-first-certificate", RegExp], readonly ["unable-to-get-local-issuer-certificate", RegExp], readonly ["unable-to-get-issuer-certificate", RegExp], readonly ["self-signed-certificate-in-certificate-chain", RegExp], readonly ["self-signed-certificate", RegExp], readonly ["certificate-not-trusted", RegExp]];
12
+ export type ShellTrustPatternId = (typeof SHELL_TRUST_PATTERNS)[number][0];
13
+ export type ShellTrust = {
14
+ readonly status: {
15
+ readonly code: "trust-import-degraded";
16
+ readonly reason: (typeof ENGINE_TRUST_FALLBACK_REASONS)[number];
17
+ readonly action: EngineTrustActionId;
18
+ };
19
+ readonly hint?: {
20
+ readonly code: "tls-verify-text-in-output";
21
+ readonly heuristic: true;
22
+ readonly pattern: ShellTrustPatternId;
23
+ };
24
+ };
25
+ export type ShellTrustAnnotation = {
26
+ readonly trust: ShellTrust;
27
+ /** The one fixed line appended for the model, only with a hint; it never echoes output. */
28
+ readonly text: string | undefined;
29
+ };
30
+ /** Nothing without an adopted fallback generation, on exit 0, or for a command that has no exit code. */
31
+ export declare function annotateShellTrust({ exitCode, output, }: {
32
+ readonly exitCode: number | null;
33
+ readonly output: string;
34
+ }): ShellTrustAnnotation | undefined;
35
+ export {};
36
+ //# sourceMappingURL=shell-trust.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The trust annotation on agent-shell results (omo#10762, design r13 6.7). Under a fallback generation a
3
+ * command that exits non-zero carries a trusted status taken from the adopted record and, only when its
4
+ * output looks like a certificate verification error, an untrusted hint naming the matched catalog pattern.
5
+ * The output never supplies a reason or an action: any command can print any text, including a forged
6
+ * Bun footer or trust line (design M16). Like `adopted.ts`, this module does not load the marker parser.
7
+ */
8
+ import { engineTrustAction } from "./actions.js";
9
+ import { adoptedEngineTrust } from "./adopted.js";
10
+ /** A verification code (design 6.6) as a whole token, whatever quotes surround it. */
11
+ const token = (code) => new RegExp(`(?<![A-Za-z0-9_])${code}(?![A-Za-z0-9_])`, "u");
12
+ /** Catalog order decides which entry a hint names when several match. */
13
+ const SHELL_TRUST_PATTERNS = [
14
+ ["UNABLE_TO_GET_ISSUER_CERT_LOCALLY", token("UNABLE_TO_GET_ISSUER_CERT_LOCALLY")],
15
+ ["UNABLE_TO_GET_ISSUER_CERT", token("UNABLE_TO_GET_ISSUER_CERT")],
16
+ ["UNABLE_TO_VERIFY_LEAF_SIGNATURE", token("UNABLE_TO_VERIFY_LEAF_SIGNATURE")],
17
+ ["SELF_SIGNED_CERT_IN_CHAIN", token("SELF_SIGNED_CERT_IN_CHAIN")],
18
+ ["DEPTH_ZERO_SELF_SIGNED_CERT", token("DEPTH_ZERO_SELF_SIGNED_CERT")],
19
+ ["CERT_UNTRUSTED", token("CERT_UNTRUSTED")],
20
+ ["unable-to-verify-the-first-certificate", /unable to verify the first certificate/iu],
21
+ ["unable-to-get-local-issuer-certificate", /unable to get local issuer certificate/iu],
22
+ ["unable-to-get-issuer-certificate", /unable to get issuer certificate/iu],
23
+ ["self-signed-certificate-in-certificate-chain", /self[- ]signed certificate in certificate chain/iu],
24
+ ["self-signed-certificate", /self[- ]signed certificate/iu],
25
+ ["certificate-not-trusted", /certificate not trusted/iu],
26
+ ];
27
+ /** Nothing without an adopted fallback generation, on exit 0, or for a command that has no exit code. */
28
+ export function annotateShellTrust({ exitCode, output, }) {
29
+ const marker = adoptedEngineTrust();
30
+ if (marker?.automatic !== "fallback" || marker.fallbackReason === null || exitCode === null || exitCode === 0) {
31
+ return undefined;
32
+ }
33
+ const reason = marker.fallbackReason;
34
+ const action = engineTrustAction(reason);
35
+ const status = { code: "trust-import-degraded", reason, action: action.id };
36
+ const pattern = SHELL_TRUST_PATTERNS.find(([, matcher]) => matcher.test(output))?.[0];
37
+ if (pattern === undefined)
38
+ return { trust: { status }, text: undefined };
39
+ return {
40
+ trust: { status, hint: { code: "tls-verify-text-in-output", heuristic: true, pattern } },
41
+ text: `omo: certificate import is degraded (${reason}), so some TLS connections may fail. This command's output contains text that looks like a certificate verification error; omo has not confirmed the cause. To diagnose: ${action.text}`,
42
+ };
43
+ }
44
+ //# sourceMappingURL=shell-trust.js.map
@@ -0,0 +1,12 @@
1
+ import { type ProviderDiagnostic } from "@earendil-works/pi-ai";
2
+ import { type EngineTrustActionId } from "./actions.ts";
3
+ import type { ENGINE_TRUST_FALLBACK_REASONS } from "./marker.ts";
4
+ type FallbackReason = (typeof ENGINE_TRUST_FALLBACK_REASONS)[number];
5
+ export interface TransportTrustFailure {
6
+ readonly code: "trust-tls-failed-under-fallback";
7
+ readonly reason: FallbackReason;
8
+ readonly action: EngineTrustActionId;
9
+ }
10
+ export declare function transportTrustFailure(diagnostic: ProviderDiagnostic | undefined): TransportTrustFailure | undefined;
11
+ export {};
12
+ //# sourceMappingURL=transport-failure.d.ts.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The `trust` object a provider failure carries when the request failed TLS chain verification while
3
+ * this engine runs on a fallback trust generation (omo#10762, design r13 6.6; senpi#5244). The code
4
+ * comes only from the adapter's structured transport diagnostic, which reads the native error's
5
+ * `code`, never message text. The object and its diagnostics record name no host, path or certificate.
6
+ */
7
+ import { appendFile } from "node:fs/promises";
8
+ import { dirname, join } from "node:path";
9
+ import { isTlsChainVerificationCode } from "@earendil-works/pi-ai";
10
+ import { engineTrustAction } from "./actions.js";
11
+ import { adoptedEngineTrust } from "./adopted.js";
12
+ /** Reasons this process already wrote a record for: one record per process and reason. */
13
+ const recordedReasons = new Set();
14
+ export function transportTrustFailure(diagnostic) {
15
+ if (diagnostic?.evidence !== "structured_transport" || diagnostic.code === undefined)
16
+ return undefined;
17
+ if (!isTlsChainVerificationCode(diagnostic.code))
18
+ return undefined;
19
+ const trust = adoptedEngineTrust();
20
+ if (trust?.automatic !== "fallback" || trust.fallbackReason === null)
21
+ return undefined;
22
+ const reason = trust.fallbackReason;
23
+ if (!recordedReasons.has(reason)) {
24
+ recordedReasons.add(reason);
25
+ appendDiagnosticsRecord(trust.file, {
26
+ v: 1,
27
+ at: new Date().toISOString(),
28
+ pid: process.pid,
29
+ generation: trust.sha256,
30
+ codes: ["trust-tls-failed-under-fallback", reason],
31
+ });
32
+ }
33
+ return { code: "trust-tls-failed-under-fallback", reason, action: engineTrustAction(reason).id };
34
+ }
35
+ /**
36
+ * One line, in one append, to `diagnostics.jsonl` beside the generation file (design 5.3). The append
37
+ * is asynchronous because this runs on the RPC session path, which takes no blocking filesystem call
38
+ * (no-sync-in-session-path). A record that cannot be written costs only the record: the failure still
39
+ * carries its `trust` object.
40
+ */
41
+ function appendDiagnosticsRecord(generationFile, record) {
42
+ appendFile(join(dirname(generationFile), "diagnostics.jsonl"), `${JSON.stringify(record)}\n`, {
43
+ mode: 0o600,
44
+ }).catch(() => {
45
+ // The trust directory may be gone or read-only; the record is diagnostic only.
46
+ });
47
+ }
48
+ //# sourceMappingURL=transport-failure.js.map
@@ -46,6 +46,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
46
46
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
47
47
  import { formatProviderFailure } from "../../core/provider-failure.js";
48
48
  import { failureAlreadyDescribed, headNoticeShownElsewhere } from "./provider-error-description.js";
49
+ import { describeFallbackExhaustion } from "../../core/retry-fallback/exhaustion.js";
49
50
  import { usageLimitCause } from "../../core/retry-fallback/usage-limit.js";
50
51
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
51
52
  import { createSessionLogger } from "../../core/session-log.js";
@@ -4864,19 +4865,24 @@ export class InteractiveMode {
4864
4865
  });
4865
4866
  this.setExtensionStatus(FALLBACK_STATUS_KEY, undefined);
4866
4867
  break;
4867
- case "retry_fallback_exhausted":
4868
- if (isRetryableProviderError(event.lastError)) {
4868
+ case "retry_fallback_exhausted": {
4869
+ // A walk that started on another failure must lead with it, not the last rung's (senpi#4084).
4870
+ const startedElsewhere = (event.failures?.length ?? 0) > 1;
4871
+ if (isRetryableProviderError(event.lastError) && !startedElsewhere) {
4869
4872
  this.getProviderErrors().finish(event.lastError);
4870
4873
  this.setExtensionStatus(FALLBACK_STATUS_KEY, undefined);
4871
4874
  break;
4872
4875
  }
4876
+ if (isRetryableProviderError(event.lastError))
4877
+ this.getProviderErrors().clear();
4873
4878
  this.showNoticeBox({
4874
4879
  title: `✕ Fallback chain exhausted · ${event.chainKey}`,
4875
4880
  tone: "error",
4876
- why: event.lastError,
4881
+ why: describeFallbackExhaustion(event),
4877
4882
  });
4878
4883
  this.setExtensionStatus(FALLBACK_STATUS_KEY, undefined);
4879
4884
  break;
4885
+ }
4880
4886
  case "server_fallback_aborted":
4881
4887
  this.showNoticeBox({
4882
4888
  title: `⚠ Server fallback aborted · ${event.from} → ${event.to}`,
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { describeProviderFailureForUser, stripTurnRetrySuppressionPrefix, } from "@earendil-works/pi-ai";
9
9
  import { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from "../core/output-guard.js";
10
+ import { describeFallbackExhaustion } from "../core/retry-fallback/exhaustion.js";
10
11
  import { usageLimitCause, usageLimitScope } from "../core/retry-fallback/usage-limit.js";
11
12
  import { killTrackedDetachedChildren } from "../utils/shell.js";
12
13
  import { toJsonEvent } from "./json-event.js";
@@ -107,7 +108,7 @@ export async function runPrintMode(runtimeHost, options) {
107
108
  console.error(`Model fallback: ${event.from} -> ${event.to} (${usageLimitCause(event.from, event.limit) ?? event.reason})`);
108
109
  }
109
110
  else if (event.type === "retry_fallback_exhausted") {
110
- console.error(`Model fallback exhausted: ${event.chainKey} (${event.lastError})`);
111
+ console.error(`Model fallback exhausted: ${event.chainKey} (${describeFallbackExhaustion(event).replaceAll("\n", "; ")})`);
111
112
  }
112
113
  else if (event.type === "retry_fallback_reverted") {
113
114
  const cause = event.cause === "fallback-unusable" ? ` (${event.from} cannot serve right now)` : "";
@@ -1,3 +1,4 @@
1
+ import { type FamilyRegistry } from "./compact-catalog-registry.ts";
1
2
  import type { CompactContentStore } from "./compact-content-store.ts";
2
3
  import type { CatalogComponent } from "./compact-recovery-catalog.ts";
3
4
  import type { SettledReader } from "./compact-settled-messages.ts";
@@ -16,8 +17,10 @@ interface CatalogCut {
16
17
  * the members it enumerated and never an explicit empty, so a reader can tell "no members" from "not enumerated"
17
18
  * (RULING-60 Q1, RULING-61 Q1). Members stream one at a time: a value goes through `walk` into bounded leaves, a
18
19
  * collection through `rows`, and each component's leaves are released once the catalog has placed it. The
19
- * completeness gate runs last: a complete producer that emitted nothing refuses the whole catalog (X03).
20
+ * completeness gate runs last: a complete producer that emitted nothing refuses the whole catalog (X03). Its return
21
+ * value is the registry it emitted as `families/families`, the one source the manifest copies `requiredKinds` from
22
+ * (RULING-128).
20
23
  */
21
- export declare function checkpointComponents(cut: CatalogCut, valid: () => void): AsyncGenerator<CatalogComponent>;
24
+ export declare function checkpointComponents(cut: CatalogCut, valid: () => void): AsyncGenerator<CatalogComponent, FamilyRegistry>;
22
25
  export {};
23
26
  //# sourceMappingURL=compact-catalog-families.d.ts.map
@@ -18,7 +18,9 @@ const DIALOGS = familyId("dialogs");
18
18
  * the members it enumerated and never an explicit empty, so a reader can tell "no members" from "not enumerated"
19
19
  * (RULING-60 Q1, RULING-61 Q1). Members stream one at a time: a value goes through `walk` into bounded leaves, a
20
20
  * collection through `rows`, and each component's leaves are released once the catalog has placed it. The
21
- * completeness gate runs last: a complete producer that emitted nothing refuses the whole catalog (X03).
21
+ * completeness gate runs last: a complete producer that emitted nothing refuses the whole catalog (X03). Its return
22
+ * value is the registry it emitted as `families/families`, the one source the manifest copies `requiredKinds` from
23
+ * (RULING-128).
22
24
  */
23
25
  export async function* checkpointComponents(cut, valid) {
24
26
  const { store, values, root, settled } = cut;
@@ -26,7 +28,7 @@ export async function* checkpointComponents(cut, valid) {
26
28
  if (!current)
27
29
  throw new CompactReadError("unavailable");
28
30
  const reader = { store, values, valid };
29
- const producers = familyProducers(root.transcript, root.refusedCorrelations);
31
+ const producers = familyProducers(root);
30
32
  const registry = familyRegistry(producers);
31
33
  yield* textComponent(store, FAMILIES, FAMILIES, JSON.stringify(registry));
32
34
  const family = (name) => {
@@ -66,6 +68,7 @@ export async function* checkpointComponents(cut, valid) {
66
68
  yield component;
67
69
  }
68
70
  assertCoverage(registry, emitted);
71
+ return registry;
69
72
  }
70
73
  async function* dialogComponents(reader, ref) {
71
74
  const dialogs = new CompactComponentRoots(reader.store, () => []);
@@ -26,7 +26,10 @@ export declare const FAMILY_PRODUCERS: FamilyProducers;
26
26
  * correlation refused at the protocol depth limit never became the lane's standing one, so `identity` is then
27
27
  * `partial` too: its member reports the count (RULING-67 (4)).
28
28
  */
29
- export declare function familyProducers(transcript: TranscriptRoot, refusedCorrelations: number): FamilyProducers;
29
+ export declare function familyProducers(root: {
30
+ readonly transcript: TranscriptRoot;
31
+ readonly refusedCorrelations: number;
32
+ }): FamilyProducers;
30
33
  /**
31
34
  * Every engine source kind (the session file's entry union) and the family that owns its data. The compiler refuses
32
35
  * a kind this map misses (TS2741), so a new engine entry type cannot ship uncataloged. A kind outside the union at
@@ -46,6 +49,11 @@ interface RegistryBase {
46
49
  readonly format: "senpi-recovery-families";
47
50
  readonly version: 1;
48
51
  readonly families: readonly FamilyRow[];
52
+ /**
53
+ * The families a root built from these producers requires (RULING-128): every `complete` one, in code-unit order,
54
+ * each once. The manifest carries this exact list, and this component's digest is inside `checkpointDigest`.
55
+ */
56
+ readonly requiredKinds: readonly CatalogFamily[];
49
57
  }
50
58
  /**
51
59
  * Set only by `familyRegistry`, which `readFamilyRegistry` decodes through and `completeAssembly` narrows to, so no other
@@ -46,7 +46,8 @@ export const FAMILY_PRODUCERS = {
46
46
  * correlation refused at the protocol depth limit never became the lane's standing one, so `identity` is then
47
47
  * `partial` too: its member reports the count (RULING-67 (4)).
48
48
  */
49
- export function familyProducers(transcript, refusedCorrelations) {
49
+ export function familyProducers(root) {
50
+ const { transcript, refusedCorrelations } = root;
50
51
  const exact = transcript.unavailable === null;
51
52
  const entries = exact
52
53
  ? {}
@@ -83,7 +84,11 @@ const COMPLETE = Symbol("complete family registry");
83
84
  export function familyRegistry(producers) {
84
85
  const families = CATALOG_FAMILIES.map((family) => ({ family, producer: producers[family] }));
85
86
  const incomplete = families.filter(({ producer }) => producer !== "complete").map(({ family }) => family);
86
- const base = { format: "senpi-recovery-families", version: 1, families };
87
+ const requiredKinds = families
88
+ .filter(({ producer }) => producer === "complete")
89
+ .map(({ family }) => family)
90
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
91
+ const base = { format: "senpi-recovery-families", version: 1, families, requiredKinds };
87
92
  return incomplete.length === 0
88
93
  ? { ...base, assembly: "complete", [COMPLETE]: true }
89
94
  : { ...base, assembly: "incomplete", incomplete };
@@ -133,6 +138,8 @@ export function readFamilyRegistry(text) {
133
138
  const registry = familyRegistry({ ...FAMILY_PRODUCERS, ...producers });
134
139
  if (field(value, "assembly") !== registry.assembly)
135
140
  throw new RangeError("family registry assembly mismatch");
141
+ if (JSON.stringify(field(value, "requiredKinds")) !== JSON.stringify(registry.requiredKinds))
142
+ throw new RangeError("family registry requiredKinds mismatch");
136
143
  return registry;
137
144
  }
138
145
  //# sourceMappingURL=compact-catalog-registry.js.map
@@ -0,0 +1,17 @@
1
+ import { type CatalogFamily, type FamilyRegistry } from "./compact-catalog-registry.ts";
2
+ export type RequiredKindsRefusal = "manifest_missing_required_kinds" | "manifest_unknown_required_kind" | "manifest_required_kinds_mismatch";
3
+ export type RequiredKindsRead = {
4
+ readonly ok: true;
5
+ readonly requiredKinds: readonly CatalogFamily[];
6
+ } | {
7
+ readonly ok: false;
8
+ readonly code: RequiredKindsRefusal;
9
+ };
10
+ /**
11
+ * The families a client opens for one decoded manifest (RULING-128): its `requiredKinds`, read verbatim and never
12
+ * replaced by a built-in family list. `registry` is the cut's `families/families` component as `readFamilyRegistry`
13
+ * decodes it from bytes verified against the root entry that `checkpointDigest` covers. The digest covers that
14
+ * component, not the manifest field, so the list opens only when it equals the component's complete families.
15
+ */
16
+ export declare function readRequiredKinds(manifest: unknown, registry: FamilyRegistry): RequiredKindsRead;
17
+ //# sourceMappingURL=compact-recovery-manifest-kinds.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { CATALOG_FAMILIES } from "./compact-catalog-registry.js";
2
+ const KNOWN = new Set(CATALOG_FAMILIES);
3
+ const isKnown = (kind) => typeof kind === "string" && KNOWN.has(kind);
4
+ /**
5
+ * The families a client opens for one decoded manifest (RULING-128): its `requiredKinds`, read verbatim and never
6
+ * replaced by a built-in family list. `registry` is the cut's `families/families` component as `readFamilyRegistry`
7
+ * decodes it from bytes verified against the root entry that `checkpointDigest` covers. The digest covers that
8
+ * component, not the manifest field, so the list opens only when it equals the component's complete families.
9
+ */
10
+ export function readRequiredKinds(manifest, registry) {
11
+ const listed = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "requiredKinds") : undefined;
12
+ if (!Array.isArray(listed))
13
+ return { ok: false, code: "manifest_missing_required_kinds" };
14
+ if (!listed.every(isKnown))
15
+ return { ok: false, code: "manifest_unknown_required_kind" };
16
+ const complete = registry.requiredKinds;
17
+ if (listed.length !== complete.length || listed.some((kind, index) => kind !== complete[index]))
18
+ return { ok: false, code: "manifest_required_kinds_mismatch" };
19
+ return { ok: true, requiredKinds: complete };
20
+ }
21
+ //# sourceMappingURL=compact-recovery-manifest-kinds.js.map
@@ -1,3 +1,4 @@
1
+ import type { CatalogFamily } from "./compact-catalog-registry.ts";
1
2
  import type { BoundedId, ContentDigest, ContentRef } from "./compact-recovery-ids.ts";
2
3
  import type { RecoveryRouteKind } from "./compact-recovery-types.ts";
3
4
  /** A manifest string: inline when it is a bounded protocol id, otherwise a ref to its exact stored value. */
@@ -31,6 +32,12 @@ export interface RecoveryManifest {
31
32
  readonly checkpointDigest: ContentDigest;
32
33
  readonly catalogRoot: ContentRef;
33
34
  readonly catalogRootLength: number;
35
+ /**
36
+ * The catalog families this cut's root requires (RULING-128): its complete families only, sorted, each once. It is
37
+ * the `requiredKinds` of the cut's `families/families` registry, so `checkpointDigest` covers it; a client opens
38
+ * exactly these and never falls back to a built-in family list.
39
+ */
40
+ readonly requiredKinds: readonly CatalogFamily[];
34
41
  }
35
42
  export declare const TEXT_FIELDS: readonly ["socketEpoch", "admissionId", "session", "sessionEpoch", "token", "leaf", "activeMessage", "turn"];
36
43
  export type TextField = (typeof TEXT_FIELDS)[number];
@@ -128,12 +128,19 @@ export class RecoveryManifests {
128
128
  valid();
129
129
  };
130
130
  const cut = { store, values: new CompactValues(store), root, settled: lane.settledOf(root) };
131
+ const emitted = {};
131
132
  const components = async function* () {
132
133
  yield* oversized.map(({ component }) => component);
133
- yield* checkpointComponents(cut, check);
134
+ emitted.registry = yield* checkpointComponents(cut, check);
134
135
  };
135
136
  const built = await this.catalogs.build(store, ref, components(), check);
136
- return await this.assemble(store, token, ref, built, oversized, texts);
137
+ // The list is the cataloged registry's own (RULING-128): the bytes `checkpointDigest` covers, never a recompute.
138
+ const requiredKinds = emitted.registry?.requiredKinds;
139
+ if (!requiredKinds) {
140
+ this.catalogs.release(store, built);
141
+ throw new CompactReadError("ownership");
142
+ }
143
+ return await this.assemble(store, token, ref, built, oversized, texts, requiredKinds);
137
144
  }
138
145
  finally {
139
146
  for (const { component } of oversized)
@@ -144,7 +151,7 @@ export class RecoveryManifests {
144
151
  });
145
152
  }
146
153
  /** Owns `built` from here: a failure anywhere below releases the tree, so no owner outlives a refused build. */
147
- async assemble(store, token, ref, built, oversized, texts) {
154
+ async assemble(store, token, ref, built, oversized, texts, requiredKinds) {
148
155
  try {
149
156
  let leftmost = ref;
150
157
  for (let level = 1; level < built.depth; level++)
@@ -183,6 +190,7 @@ export class RecoveryManifests {
183
190
  checkpointDigest: built.digest,
184
191
  catalogRoot: ref,
185
192
  catalogRootLength: built.length,
193
+ requiredKinds,
186
194
  };
187
195
  return { manifest, root: built, store };
188
196
  }
@@ -1,11 +1,15 @@
1
1
  import { compactUpdatesEnabled } from "../../core/engine-socket-flag.js";
2
2
  import { CONNECTION_ROLE_FIXED, roleConnection } from "./connection-roles.js";
3
+ import { remoteOriginDeclaration } from "./remote-origin.js";
3
4
  import { error } from "./session-router-errors.js";
4
5
  /** Validate the full declaration before committing role, media, recovery or pending state. */
5
6
  export function declareSocketClient(host, command) {
6
7
  const peer = host.writer.currentConnection();
7
8
  const selection = peer === undefined ? undefined : host.writer.compactSelection(peer);
8
9
  const role = (caps) => host.connectionRoles.declare(roleConnection(peer), caps);
10
+ const origin = remoteOriginDeclaration(command.remoteOrigin, (declared) => host.connectionRoles.acceptsOrigin(roleConnection(peer), declared));
11
+ if ("refusal" in origin)
12
+ return error(command.id, command.type, origin.refusal);
9
13
  const reason = selection
10
14
  ? selection.declare(command.capabilities, host.registry.supportsCompact?.() === true, role)
11
15
  : command.capabilities !== undefined && !role(command.capabilities)
@@ -23,6 +27,8 @@ export function declareSocketClient(host, command) {
23
27
  }
24
28
  else if (command.capabilities !== undefined)
25
29
  host.writer.setSoleSecretCapabilities(command.capabilities);
30
+ if (origin.origin !== undefined)
31
+ host.connectionRoles.declareOrigin(roleConnection(peer), origin.origin);
26
32
  return {
27
33
  id: command.id,
28
34
  type: "response",
@@ -45,6 +45,7 @@ import { DURABLE_CLIENT_MESSAGE_ID_CAPABILITY } from "./client-admission-record.
45
45
  import { ClientAdmissions } from "./client-admissions.js";
46
46
  import { handleClientInput } from "./client-input-handler.js";
47
47
  import { ConnectionQuestionBridge, degradeQuestion, sessionQuestionBridges } from "./connection-question-bridge.js";
48
+ import { remoteOriginDeclaration, remoteOriginField } from "./remote-origin.js";
48
49
  import { advertisedCompactCapabilities, sessionDeclaration } from "./compact-negotiation.js";
49
50
  import { ConnectionRoles, roleConnection } from "./connection-roles.js";
50
51
  import { AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EVAL_CELLS_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, ENTRIES_TAIL_CAPABILITY, INTERACTIVE_USER_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUESTION_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, TOOL_BARRIER_CAPABILITY, TASK_PARENT_CAPABILITY, } from "./custom-capability.js";
@@ -339,7 +340,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
339
340
  const pendingExtensionRequests = new SessionExtensionUiRequests(uiRequestChanged);
340
341
  const grantRequests = new Map();
341
342
  sessionDialogRequests.set(session, pendingExtensionRequests);
342
- const questions = new ConnectionQuestionBridge(output, uiRequestChanged, () => options.clientInfo?.connectionId());
343
+ const raiserOrigin = () => connectionRoles.originOf(options.clientInfo?.connectionId());
344
+ const questions = new ConnectionQuestionBridge(output, uiRequestChanged, () => options.clientInfo?.connectionId(), raiserOrigin);
343
345
  let secretHandshake = false;
344
346
  const secretOwners = new Map();
345
347
  const secretCapable = (connection) => connectionRoles.has(connection, INTERACTIVE_USER_CAPABILITY) &&
@@ -381,6 +383,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
381
383
  if (opts?.signal?.aborted)
382
384
  return Promise.resolve(defaultValue);
383
385
  const id = crypto.randomUUID();
386
+ const raised = { ...request, ...remoteOriginField(raiserOrigin()) };
384
387
  return new Promise((resolve, reject) => {
385
388
  let timeoutId;
386
389
  const cleanup = () => {
@@ -403,7 +406,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
403
406
  }, opts.timeout);
404
407
  }
405
408
  pendingExtensionRequests.set(id, {
406
- frame: { type: "extension_ui_request", id, ...request },
409
+ frame: { type: "extension_ui_request", id, ...raised },
407
410
  resolve: (response) => {
408
411
  cleanup();
409
412
  resolve(parseResponse(response));
@@ -412,7 +415,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
412
415
  });
413
416
  if (request.grantSelect === true)
414
417
  grantRequests.set(id, options.clientInfo?.connectionId());
415
- output({ type: "extension_ui_request", id, ...request });
418
+ output({ type: "extension_ui_request", id, ...raised });
416
419
  });
417
420
  }
418
421
  const selectGrantDialog = (title, choices, opts) => createDialogPromise(opts, undefined, {
@@ -549,9 +552,17 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
549
552
  },
550
553
  async editor(title, prefill) {
551
554
  const id = crypto.randomUUID();
555
+ const frame = {
556
+ type: "extension_ui_request",
557
+ id,
558
+ method: "editor",
559
+ title,
560
+ prefill,
561
+ ...remoteOriginField(raiserOrigin()),
562
+ };
552
563
  return new Promise((resolve, reject) => {
553
564
  pendingExtensionRequests.set(id, {
554
- frame: { type: "extension_ui_request", id, method: "editor", title, prefill },
565
+ frame,
555
566
  resolve: (response) => {
556
567
  if ("cancelled" in response && response.cancelled) {
557
568
  resolve(undefined);
@@ -565,7 +576,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
565
576
  },
566
577
  reject,
567
578
  });
568
- output({ type: "extension_ui_request", id, method: "editor", title, prefill });
579
+ output(frame);
569
580
  });
570
581
  },
571
582
  addAutocompleteProvider() {
@@ -1164,9 +1175,14 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1164
1175
  // State
1165
1176
  // =================================================================
1166
1177
  case "set_client_info": {
1178
+ const origin = remoteOriginDeclaration(command.remoteOrigin, (declared) => connectionRoles.acceptsOrigin(currentConnectionId(), declared));
1179
+ if ("refusal" in origin)
1180
+ return error(id, "set_client_info", origin.refusal);
1167
1181
  const next = sessionDeclaration(options, command, clientCapabilities, (caps) => connectionRoles.declare(currentConnectionId(), caps), replaceClientCapabilities);
1168
1182
  if (next.refusal)
1169
1183
  return error(id, "set_client_info", next.refusal);
1184
+ if (origin.origin !== undefined)
1185
+ connectionRoles.declareOrigin(currentConnectionId(), origin.origin);
1170
1186
  clientCapabilities = next.capabilities;
1171
1187
  if (command.capabilities !== undefined) {
1172
1188
  secretHandshake = true;
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionUIContext, QuestionRequest, QuestionResponse } from "../../core/extensions/types.ts";
2
2
  import type { UiRequestChange } from "./snapshot-types.ts";
3
+ import { type RpcRemoteOrigin } from "./remote-origin.ts";
3
4
  import type { RpcExtensionUIProgress, RpcExtensionUIResponse, RpcQuestionUiRequest } from "./rpc-types.ts";
4
5
  type Options = Parameters<NonNullable<ExtensionUIContext["question"]>>[1];
5
6
  export declare const sessionQuestionBridges: WeakMap<object, ConnectionQuestionBridge>;
@@ -9,7 +10,8 @@ export declare class ConnectionQuestionBridge {
9
10
  private readonly output;
10
11
  private readonly changed;
11
12
  private readonly raiser;
12
- constructor(output: (record: object) => void, changed?: (change?: UiRequestChange) => void, raiser?: () => string | undefined);
13
+ private readonly origin;
14
+ constructor(output: (record: object) => void, changed?: (change?: UiRequestChange) => void, raiser?: () => string | undefined, origin?: () => RpcRemoteOrigin | undefined);
13
15
  pendingQuestions(): RpcQuestionUiRequest[];
14
16
  ask(request: QuestionRequest, opts?: Options): Promise<QuestionResponse>;
15
17
  respond(response: RpcExtensionUIResponse, beforeAnswer?: () => void): boolean | "question_incomplete" | "question_already_resolved";
@@ -1,12 +1,14 @@
1
1
  import { settledQuestionStatus, unansweredQuestionIds } from "./extension-ui-response.js";
2
+ import { remoteOriginField } from "./remote-origin.js";
2
3
  export const sessionQuestionBridges = new WeakMap();
3
4
  export class ConnectionQuestionBridge {
4
- constructor(output, changed, raiser = () => undefined) {
5
+ constructor(output, changed, raiser = () => undefined, origin = () => undefined) {
5
6
  this.pending = new Map();
6
7
  this.resolved = new Set();
7
8
  this.output = output;
8
9
  this.changed = changed;
9
10
  this.raiser = raiser;
11
+ this.origin = origin;
10
12
  }
11
13
  pendingQuestions() {
12
14
  return [...this.pending.values()].map(({ frame }) => ({
@@ -27,6 +29,7 @@ export class ConnectionQuestionBridge {
27
29
  waitForAnswer: request.waitForAnswer,
28
30
  questions: request.questions,
29
31
  raisedBy: this.raiser() ?? "agent",
32
+ ...remoteOriginField(this.origin()),
30
33
  timeout,
31
34
  askedAtMs,
32
35
  deadlineAtMs: askedAtMs + timeout,