@theokit/sdk 5.12.1 → 5.14.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.
Files changed (193) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/{agent-Cn0sphpA.d.cts → agent-DExqjqYy.d.cts} +18 -3
  3. package/dist/{agent-CVzDy_Oj.d.ts → agent-Dfw3VsIX.d.ts} +18 -3
  4. package/dist/{agent-IG7BP6GN.js → agent-T34LCGQM.js} +11 -11
  5. package/dist/{agent-IG7BP6GN.js.map → agent-T34LCGQM.js.map} +1 -1
  6. package/dist/{agent-BVKIUP7F.cjs → agent-TL4K7BTG.cjs} +12 -12
  7. package/dist/{agent-BVKIUP7F.cjs.map → agent-TL4K7BTG.cjs.map} +1 -1
  8. package/dist/auth/index.cjs +17 -16
  9. package/dist/auth/index.cjs.map +1 -1
  10. package/dist/auth/index.js +5 -4
  11. package/dist/auth/index.js.map +1 -1
  12. package/dist/{chunk-NHOEXFZT.js → chunk-2HXTNVLM.js} +5 -2
  13. package/dist/chunk-2HXTNVLM.js.map +1 -0
  14. package/dist/{chunk-Q6WT5535.cjs → chunk-2WY2OFSC.cjs} +259 -79
  15. package/dist/chunk-2WY2OFSC.cjs.map +1 -0
  16. package/dist/{chunk-HN4IKB6X.cjs → chunk-2XWAYCFM.cjs} +15 -13
  17. package/dist/chunk-2XWAYCFM.cjs.map +1 -0
  18. package/dist/{chunk-3FSBE4PS.js → chunk-3GTN4HOO.js} +10 -6
  19. package/dist/chunk-3GTN4HOO.js.map +1 -0
  20. package/dist/{chunk-VVGFZQZC.js → chunk-3LMVJTNJ.js} +9 -5
  21. package/dist/chunk-3LMVJTNJ.js.map +1 -0
  22. package/dist/{chunk-4GUFNDZR.js → chunk-3ONIVO2I.js} +3 -3
  23. package/dist/{chunk-4GUFNDZR.js.map → chunk-3ONIVO2I.js.map} +1 -1
  24. package/dist/{chunk-7WARNHWW.cjs → chunk-3UMPP553.cjs} +5 -2
  25. package/dist/chunk-3UMPP553.cjs.map +1 -0
  26. package/dist/{chunk-NWWTYBD3.cjs → chunk-4KBI37F3.cjs} +5 -5
  27. package/dist/{chunk-NWWTYBD3.cjs.map → chunk-4KBI37F3.cjs.map} +1 -1
  28. package/dist/{chunk-S46WJTDL.cjs → chunk-4TEGFMSP.cjs} +6 -6
  29. package/dist/{chunk-S46WJTDL.cjs.map → chunk-4TEGFMSP.cjs.map} +1 -1
  30. package/dist/{chunk-QISSETTG.cjs → chunk-5645PMVU.cjs} +6 -4
  31. package/dist/chunk-5645PMVU.cjs.map +1 -0
  32. package/dist/{chunk-ZLFFDPXC.js → chunk-73IITAF7.js} +36 -14
  33. package/dist/chunk-73IITAF7.js.map +1 -0
  34. package/dist/{chunk-5A4UFBHW.cjs → chunk-A77UMVMD.cjs} +15 -11
  35. package/dist/chunk-A77UMVMD.cjs.map +1 -0
  36. package/dist/{chunk-IO4RI5ZJ.cjs → chunk-BZK5QL3N.cjs} +4 -4
  37. package/dist/{chunk-IO4RI5ZJ.cjs.map → chunk-BZK5QL3N.cjs.map} +1 -1
  38. package/dist/{chunk-5HSMH2ZZ.js → chunk-CJN5CEI3.js} +4 -4
  39. package/dist/{chunk-5HSMH2ZZ.js.map → chunk-CJN5CEI3.js.map} +1 -1
  40. package/dist/{chunk-XRPMZ7TO.js → chunk-CYWOJA6B.js} +6 -4
  41. package/dist/chunk-CYWOJA6B.js.map +1 -0
  42. package/dist/{chunk-AQW54OWQ.js → chunk-DFBWYETS.js} +5 -3
  43. package/dist/chunk-DFBWYETS.js.map +1 -0
  44. package/dist/{chunk-EPANMGH7.cjs → chunk-FLDHYF5C.cjs} +11 -2
  45. package/dist/chunk-FLDHYF5C.cjs.map +1 -0
  46. package/dist/{chunk-BJ7BPLQK.js → chunk-GARVAUCU.js} +209 -29
  47. package/dist/chunk-GARVAUCU.js.map +1 -0
  48. package/dist/{chunk-YOVZMN4P.cjs → chunk-LXG6VL6R.cjs} +3 -3
  49. package/dist/{chunk-YOVZMN4P.cjs.map → chunk-LXG6VL6R.cjs.map} +1 -1
  50. package/dist/{chunk-NJBJ3CUG.js → chunk-NAVUTK6X.js} +3 -3
  51. package/dist/{chunk-NJBJ3CUG.js.map → chunk-NAVUTK6X.js.map} +1 -1
  52. package/dist/{chunk-2SFLLOMN.js → chunk-NVQSBRG6.js} +3 -3
  53. package/dist/{chunk-2SFLLOMN.js.map → chunk-NVQSBRG6.js.map} +1 -1
  54. package/dist/{chunk-FEAWEBWN.cjs → chunk-PDOXYXWX.cjs} +6 -4
  55. package/dist/chunk-PDOXYXWX.cjs.map +1 -0
  56. package/dist/{chunk-MS3TQSXU.js → chunk-QD6B6W6I.js} +6 -4
  57. package/dist/chunk-QD6B6W6I.js.map +1 -0
  58. package/dist/{chunk-NBNRQN6T.js → chunk-QGDPMM3C.js} +11 -2
  59. package/dist/chunk-QGDPMM3C.js.map +1 -0
  60. package/dist/{chunk-ZWLFHSON.cjs → chunk-R35ZJAYT.cjs} +38 -15
  61. package/dist/chunk-R35ZJAYT.cjs.map +1 -0
  62. package/dist/{chunk-4YD2WI57.cjs → chunk-R427EOZE.cjs} +19 -15
  63. package/dist/chunk-R427EOZE.cjs.map +1 -0
  64. package/dist/chunk-R5UQL2R5.cjs +18 -0
  65. package/dist/{chunk-LO6LIUZ6.cjs.map → chunk-R5UQL2R5.cjs.map} +1 -1
  66. package/dist/{chunk-TLVI2OQK.cjs → chunk-SETHRLQS.cjs} +18 -2
  67. package/dist/chunk-SETHRLQS.cjs.map +1 -0
  68. package/dist/{chunk-LSWHDOCL.cjs → chunk-SNVYOX46.cjs} +88 -72
  69. package/dist/chunk-SNVYOX46.cjs.map +1 -0
  70. package/dist/{chunk-BO3PV3OP.js → chunk-TDED5XES.js} +18 -3
  71. package/dist/chunk-TDED5XES.js.map +1 -0
  72. package/dist/{chunk-NKQVLWDF.js → chunk-ULEK4OM4.js} +3 -3
  73. package/dist/{chunk-NKQVLWDF.js.map → chunk-ULEK4OM4.js.map} +1 -1
  74. package/dist/{chunk-XJ4C4NFX.js → chunk-ZOL3L4EH.js} +63 -49
  75. package/dist/chunk-ZOL3L4EH.js.map +1 -0
  76. package/dist/{compact-session-NM56BREJ.cjs → compact-session-D2MQDB6G.cjs} +15 -15
  77. package/dist/{compact-session-NM56BREJ.cjs.map → compact-session-D2MQDB6G.cjs.map} +1 -1
  78. package/dist/{compact-session-3U2JYTVG.js → compact-session-N2PPHQO6.js} +6 -6
  79. package/dist/{compact-session-3U2JYTVG.js.map → compact-session-N2PPHQO6.js.map} +1 -1
  80. package/dist/{cron-CHn-F0fC.d.ts → cron-BubDpFJG.d.ts} +2 -2
  81. package/dist/{cron-BWDYetbq.d.cts → cron-CK2v0VWA.d.cts} +2 -2
  82. package/dist/cron.cjs +11 -11
  83. package/dist/cron.d.cts +3 -3
  84. package/dist/cron.d.ts +3 -3
  85. package/dist/cron.js +10 -10
  86. package/dist/eval.cjs +15 -15
  87. package/dist/eval.js +10 -10
  88. package/dist/{executor-HKHT4CWS.cjs → executor-5QL5AXIZ.cjs} +18 -15
  89. package/dist/executor-5QL5AXIZ.cjs.map +1 -0
  90. package/dist/{executor-3D2UDJHW.js → executor-THUXNAFR.js} +6 -3
  91. package/dist/executor-THUXNAFR.js.map +1 -0
  92. package/dist/generate-object-A5F576IT.cjs +20 -0
  93. package/dist/{generate-object-XBWD2PU7.cjs.map → generate-object-A5F576IT.cjs.map} +1 -1
  94. package/dist/{generate-object-S7MNVF2V.js → generate-object-TBDOBBYW.js} +4 -4
  95. package/dist/{generate-object-S7MNVF2V.js.map → generate-object-TBDOBBYW.js.map} +1 -1
  96. package/dist/generate-object.d.ts +14 -1
  97. package/dist/index.cjs +81 -65
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.d.cts +36 -7
  100. package/dist/index.d.ts +36 -7
  101. package/dist/index.js +44 -28
  102. package/dist/index.js.map +1 -1
  103. package/dist/{inject-session-JVTZNQF6.cjs → inject-session-3ICZIJ2N.cjs} +4 -4
  104. package/dist/{inject-session-JVTZNQF6.cjs.map → inject-session-3ICZIJ2N.cjs.map} +1 -1
  105. package/dist/{inject-session-THABTKJY.js → inject-session-J26SHHIO.js} +3 -3
  106. package/dist/{inject-session-THABTKJY.js.map → inject-session-J26SHHIO.js.map} +1 -1
  107. package/dist/internal/agent-loop/prune-tool-results.d.ts +37 -0
  108. package/dist/internal/auth/credential-store.d.ts +1 -1
  109. package/dist/internal/auth/types.d.ts +4 -0
  110. package/dist/internal/providers/discovery.d.ts +2 -2
  111. package/dist/internal/providers/trust-gate.d.ts +35 -0
  112. package/dist/internal/runtime/plugin-loader/manifest-entry-import.d.ts +17 -0
  113. package/dist/internal/runtime/plugin-loader/workspace-plugin-trust.d.ts +16 -0
  114. package/dist/internal/runtime/skills/binary-on-path.d.ts +1 -0
  115. package/dist/internal/runtime/skills/discover-skills.d.ts +2 -0
  116. package/dist/internal/structured-output-helpers.d.ts +5 -2
  117. package/dist/mcp-auth.cjs +2 -2
  118. package/dist/mcp-auth.js +1 -1
  119. package/dist/models.cjs +11 -11
  120. package/dist/models.js +4 -4
  121. package/dist/providers.cjs +6 -6
  122. package/dist/providers.js +4 -4
  123. package/dist/sandbox/bwrap.d.cts +10 -8
  124. package/dist/sandbox/bwrap.d.ts +10 -8
  125. package/dist/sandbox/deny-read.d.cts +24 -0
  126. package/dist/sandbox/deny-read.d.ts +24 -0
  127. package/dist/sandbox/index.cjs +114 -35
  128. package/dist/sandbox/index.cjs.map +1 -1
  129. package/dist/sandbox/index.d.cts +2 -1
  130. package/dist/sandbox/index.d.ts +2 -1
  131. package/dist/sandbox/index.js +96 -21
  132. package/dist/sandbox/index.js.map +1 -1
  133. package/dist/sandbox/linux-sandbox.d.cts +16 -1
  134. package/dist/sandbox/linux-sandbox.d.ts +16 -1
  135. package/dist/sandbox/local-sandbox.d.cts +3 -1
  136. package/dist/sandbox/local-sandbox.d.ts +3 -1
  137. package/dist/sandbox/seccomp-delivery.d.cts +3 -0
  138. package/dist/sandbox/seccomp-delivery.d.ts +3 -0
  139. package/dist/sandbox/shell-escape.d.cts +14 -2
  140. package/dist/sandbox/shell-escape.d.ts +14 -2
  141. package/dist/sandbox/types.d.cts +45 -0
  142. package/dist/sandbox/types.d.ts +45 -0
  143. package/dist/{sdk-agent-4-dXI2GS.d.cts → sdk-agent-DwzHcuxy.d.cts} +28 -3
  144. package/dist/{sdk-agent-CTOJlWA_.d.ts → sdk-agent-a3fW1xUK.d.ts} +28 -3
  145. package/dist/skills.cjs +4 -4
  146. package/dist/skills.js +1 -1
  147. package/dist/stream-object-AS4YYM4B.cjs +20 -0
  148. package/dist/{stream-object-TEEK3CAX.cjs.map → stream-object-AS4YYM4B.cjs.map} +1 -1
  149. package/dist/stream-object-EKM4WGTI.js +7 -0
  150. package/dist/{stream-object-R6SMYV4R.js.map → stream-object-EKM4WGTI.js.map} +1 -1
  151. package/dist/stream-object.d.ts +14 -1
  152. package/dist/subagents-loader.d.cts +2 -2
  153. package/dist/subagents-loader.d.ts +2 -2
  154. package/dist/types/agent.d.ts +14 -1
  155. package/dist/types/run.d.ts +27 -2
  156. package/dist/workflow-as-tool.d.ts +39 -5
  157. package/dist/workflow-errors.d.ts +16 -0
  158. package/dist/workflow.cjs +31 -23
  159. package/dist/workflow.d.cts +55 -5
  160. package/dist/workflow.d.ts +55 -5
  161. package/dist/workflow.js +2 -2
  162. package/docs/adr/mutation-testing-scope.md +56 -0
  163. package/docs/error-codes.md +23 -18
  164. package/docs/harness-capability-map.md +78 -4
  165. package/package.json +1 -1
  166. package/dist/chunk-3FSBE4PS.js.map +0 -1
  167. package/dist/chunk-4YD2WI57.cjs.map +0 -1
  168. package/dist/chunk-5A4UFBHW.cjs.map +0 -1
  169. package/dist/chunk-7WARNHWW.cjs.map +0 -1
  170. package/dist/chunk-AQW54OWQ.js.map +0 -1
  171. package/dist/chunk-BJ7BPLQK.js.map +0 -1
  172. package/dist/chunk-BO3PV3OP.js.map +0 -1
  173. package/dist/chunk-EPANMGH7.cjs.map +0 -1
  174. package/dist/chunk-FEAWEBWN.cjs.map +0 -1
  175. package/dist/chunk-HN4IKB6X.cjs.map +0 -1
  176. package/dist/chunk-LO6LIUZ6.cjs +0 -18
  177. package/dist/chunk-LSWHDOCL.cjs.map +0 -1
  178. package/dist/chunk-MS3TQSXU.js.map +0 -1
  179. package/dist/chunk-NBNRQN6T.js.map +0 -1
  180. package/dist/chunk-NHOEXFZT.js.map +0 -1
  181. package/dist/chunk-Q6WT5535.cjs.map +0 -1
  182. package/dist/chunk-QISSETTG.cjs.map +0 -1
  183. package/dist/chunk-TLVI2OQK.cjs.map +0 -1
  184. package/dist/chunk-VVGFZQZC.js.map +0 -1
  185. package/dist/chunk-XJ4C4NFX.js.map +0 -1
  186. package/dist/chunk-XRPMZ7TO.js.map +0 -1
  187. package/dist/chunk-ZLFFDPXC.js.map +0 -1
  188. package/dist/chunk-ZWLFHSON.cjs.map +0 -1
  189. package/dist/executor-3D2UDJHW.js.map +0 -1
  190. package/dist/executor-HKHT4CWS.cjs.map +0 -1
  191. package/dist/generate-object-XBWD2PU7.cjs +0 -20
  192. package/dist/stream-object-R6SMYV4R.js +0 -7
  193. package/dist/stream-object-TEEK3CAX.cjs +0 -20
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/internal/auth/oauth-device.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * M42 — the SDK OAuth device-authorization flows (RFC 8628 + OpenAI two-step headless). Split out of\n * oauth-engine.ts to respect the 500-line file budget; a sibling of the core engine. Promoted DOWN from\n * agent-builder's hardened `agents/lib/oauth-device.ts` (M37).\n *\n * Implements two device flows against their published specs: the RFC 8628 device-grant poll loop\n * (device_code → authorization_pending / slow_down) and OpenAI's two-step headless flow, including the\n * JWT-claim / account-id extraction used to attribute an account.\n * Design choices: network I/O + clock + sleep are INJECTED (deterministic tests), failures raise the SDK's\n * typed `AuthCallbackError`, and a device-code expiry DEADLINE guards each poll loop.\n *\n * @internal\n */\nimport { AuthCallbackError } from \"../../server/auth/errors.js\";\nimport { exchangeCode } from \"./oauth-engine.js\";\nimport type {\n DeviceCodeGrant,\n DeviceDeps,\n DeviceOAuthConfig,\n OAuthTokens,\n OpenAIDeviceConfig,\n} from \"./types.js\";\n\n// ─── OAuth 2.0 Device Authorization Grant (RFC 8628) — terminal-first / headless login ───\n\n/** Buffer added to every poll interval to avoid hitting the server a hair early. */\nconst POLLING_SAFETY_MARGIN_MS = 3000;\n\n/**\n * Parse a JSON body, or fail with this module's typed error instead of a raw `SyntaxError`.\n *\n * Every failure in this file is supposed to reach the caller as an `AuthCallbackError` carrying a code\n * the CLI can branch on. `res.json()` breaks that contract: a device endpoint that answers with HTML —\n * a captive portal, a corporate proxy's sign-in page, a load balancer's error page — makes it reject\n * with a `SyntaxError` that no `catch` here handles, so it escapes untyped past a caller prepared only\n * for `AuthCallbackError`.\n *\n * The body is quoted, truncated, because \"not JSON\" and \"not JSON, and it looks like a proxy login\n * page\" are different diagnoses for whoever is holding the terminal — and routing them to the provider\n * when the fault is their network is the expensive kind of wrong.\n */\nasync function parseJsonOrFail(res: Response, code: string, what: string): Promise<unknown> {\n const text = await res.text().catch(() => \"\");\n try {\n return JSON.parse(text);\n } catch {\n throw new AuthCallbackError(code, `${what} returned a non-JSON body: ${text.slice(0, 120)}`);\n }\n}\n\n/** Step 1 — request a device code. POSTs `{client_id, scope}` to the device endpoint. */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: RFC 8628 device-code request — cohesive network response parser ported verbatim (ADR D3).\nexport async function requestDeviceCode(\n config: DeviceOAuthConfig,\n deps: Pick<DeviceDeps, \"fetch\">,\n): Promise<DeviceCodeGrant> {\n let res: Response;\n try {\n res = await deps.fetch(config.deviceCodeEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body: new URLSearchParams({\n client_id: config.clientId,\n scope: config.scopes.join(\" \"),\n }).toString(),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `device request failed: ${(err as Error).message}`,\n );\n }\n if (!res.ok) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `device endpoint returned HTTP ${res.status}`,\n );\n }\n const data = (await parseJsonOrFail(\n res,\n \"oauth_device_authorization_failed\",\n \"device endpoint\",\n )) as {\n device_code?: unknown;\n user_code?: unknown;\n verification_uri?: unknown;\n verification_uri_complete?: unknown;\n interval?: unknown;\n expires_in?: unknown;\n };\n if (typeof data.device_code !== \"string\" || typeof data.user_code !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n \"device response missing device_code/user_code\",\n );\n }\n const verification =\n typeof data.verification_uri === \"string\"\n ? data.verification_uri\n : typeof data.verification_uri_complete === \"string\"\n ? data.verification_uri_complete\n : \"\";\n return {\n deviceCode: data.device_code,\n userCode: data.user_code,\n verificationUri: verification,\n interval: typeof data.interval === \"number\" && data.interval > 0 ? data.interval : 5,\n expiresIn: typeof data.expires_in === \"number\" && data.expires_in > 0 ? data.expires_in : 900,\n };\n}\n\n/**\n * Step 2 — poll the token endpoint until the user approves (or the code expires). Handles RFC 8628\n * `authorization_pending` (keep waiting) and `slow_down` (back off), with an expiry deadline so it cannot\n * spin forever.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: RFC 8628 poll state machine (authorization_pending / slow_down / success / expiry) — a cohesive loop ported verbatim (ADR D3); fragmenting it would obscure the protocol.\nexport async function pollDeviceToken(\n config: DeviceOAuthConfig,\n grant: DeviceCodeGrant,\n deps: DeviceDeps,\n): Promise<OAuthTokens> {\n const deadline = deps.now() + grant.expiresIn * 1000;\n let intervalMs = grant.interval * 1000;\n\n while (deps.now() < deadline) {\n let res: Response;\n try {\n res = await deps.fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n accept: \"application/json\",\n },\n body: new URLSearchParams({\n client_id: config.clientId,\n device_code: grant.deviceCode,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }).toString(),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device token poll failed: ${(err as Error).message}`,\n );\n }\n // RFC 8628 §3.5: `authorization_pending` / `slow_down` arrive as a 400 with an `error` body — so the\n // body MUST be read before deciding failure. Parse first; only a non-OAuth-shaped body on a non-2xx fails.\n const data = (await res.json().catch(() => ({}))) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n id_token?: string;\n error?: string;\n interval?: number;\n };\n if (!res.ok && typeof data.error !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device token endpoint returned HTTP ${res.status}`,\n );\n }\n\n if (typeof data.access_token === \"string\" && data.access_token.length > 0) {\n const expiresIn = typeof data.expires_in === \"number\" ? data.expires_in : 0;\n const accountId = extractAccountId({\n access_token: data.access_token,\n id_token: data.id_token,\n });\n return {\n access: data.access_token,\n // Some device-grant providers (e.g. GitHub) issue no separate refresh token — reuse the access\n // token as the refresh handle.\n refresh: data.refresh_token ?? data.access_token,\n expires: deps.now() + expiresIn * 1000,\n ...(accountId !== undefined ? { accountId } : {}),\n };\n }\n\n if (data.error === \"authorization_pending\") {\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n continue;\n }\n if (data.error === \"slow_down\") {\n // RFC 8628 §3.5: add 5s to the interval, or honor a server-provided one.\n intervalMs =\n (typeof data.interval === \"number\" && data.interval > 0\n ? data.interval\n : grant.interval + 5) * 1000;\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n continue;\n }\n if (typeof data.error === \"string\") {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device authorization rejected: ${data.error}`,\n );\n }\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n }\n throw new AuthCallbackError(\n \"oauth_device_code_expired\",\n \"the device code expired before it was approved\",\n );\n}\n\n/**\n * The full device login: request a code, hand the user the verification URL + user code via `onPrompt`\n * (never printed here — the caller renders it, keeping this module output-free), then poll to completion.\n */\nexport async function deviceLogin(\n config: DeviceOAuthConfig,\n deps: DeviceDeps,\n hooks: {\n onPrompt: (p: { userCode: string; verificationUri: string; expiresIn: number }) => void;\n },\n): Promise<OAuthTokens> {\n const grant = await requestDeviceCode(config, deps);\n hooks.onPrompt({\n userCode: grant.userCode,\n verificationUri: grant.verificationUri,\n expiresIn: grant.expiresIn,\n });\n return pollDeviceToken(config, grant, deps);\n}\n\n// ─── OpenAI / ChatGPT \"headless\" device flow (two-step) ───\n\n/** Step 1 (OpenAI) — request the user code. */\nexport async function requestOpenAIUsercode(\n config: OpenAIDeviceConfig,\n deps: Pick<DeviceDeps, \"fetch\">,\n): Promise<{ deviceAuthId: string; userCode: string; interval: number }> {\n let res: Response;\n try {\n res = await deps.fetch(config.deviceUsercodeEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({ client_id: config.clientId }),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `usercode request failed: ${(err as Error).message}`,\n );\n }\n if (!res.ok) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `usercode endpoint returned HTTP ${res.status}`,\n );\n }\n const data = (await parseJsonOrFail(\n res,\n \"oauth_device_authorization_failed\",\n \"usercode endpoint\",\n )) as {\n device_auth_id?: unknown;\n user_code?: unknown;\n interval?: unknown;\n };\n if (typeof data.device_auth_id !== \"string\" || typeof data.user_code !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n \"usercode response missing device_auth_id/user_code\",\n );\n }\n const interval = Math.max(\n typeof data.interval === \"string\" ? parseInt(data.interval, 10) || 5 : 5,\n 1,\n );\n return { deviceAuthId: data.device_auth_id, userCode: data.user_code, interval };\n}\n\n/**\n * Step 2+3 (OpenAI) — poll for the authorization code (200 = ready; 403/404 = pending; else fail), then\n * exchange it for tokens at the standard `/oauth/token` endpoint (reuses `exchangeCode`).\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: OpenAI two-step headless poll loop (200 ready / 403,404 pending / else fail) — cohesive network state machine ported verbatim (ADR D3).\nexport async function openaiDeviceLogin(\n config: OpenAIDeviceConfig,\n deps: DeviceDeps,\n hooks: { onPrompt: (p: { userCode: string; verificationUri: string }) => void },\n): Promise<OAuthTokens> {\n const { deviceAuthId, userCode, interval } = await requestOpenAIUsercode(config, deps);\n hooks.onPrompt({ userCode, verificationUri: config.verificationUri });\n\n const intervalMs = interval * 1000;\n // Bound the loop so it cannot spin forever if the provider never resolves (upstream loops unbounded).\n const deadline = deps.now() + 15 * 60 * 1000;\n while (deps.now() < deadline) {\n let res: Response;\n try {\n res = await deps.fetch(config.devicePollEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({ device_auth_id: deviceAuthId, user_code: userCode }),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device poll failed: ${(err as Error).message}`,\n );\n }\n if (res.ok) {\n const data = (await parseJsonOrFail(res, \"oauth_token_exchange_failed\", \"device poll\")) as {\n authorization_code?: unknown;\n code_verifier?: unknown;\n };\n if (typeof data.authorization_code !== \"string\" || typeof data.code_verifier !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"device poll returned no authorization_code\",\n );\n }\n const tokens = await exchangeCode(\n config,\n { code: data.authorization_code, verifier: data.code_verifier },\n deps,\n );\n // M43 D4 fix #2 — the two-step exchange returns a JWT access token but `parseTokenResponse` only reads a\n // top-level `account_id`. JWT-extract the `chatgpt_account_id` (as the generic device poll already does)\n // so the login persists a non-empty account_id for the Codex `ChatGPT-Account-Id` header.\n return {\n ...tokens,\n accountId: tokens.accountId ?? extractAccountId({ access_token: tokens.access }),\n };\n }\n // 403/404 = still pending (user has not approved yet); anything else is a hard failure.\n if (res.status !== 403 && res.status !== 404) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device poll returned HTTP ${res.status}`,\n );\n }\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n }\n throw new AuthCallbackError(\n \"oauth_device_code_expired\",\n \"the device authorization expired before approval\",\n );\n}\n\n/** JWT id/access-token claims we read to attribute an account. */\ninterface IdTokenClaims {\n chatgpt_account_id?: string;\n organizations?: Array<{ id: string }>;\n \"https://api.openai.com/auth\"?: { chatgpt_account_id?: string };\n}\n\n/** Decode a JWT's claim set (no signature verification — used only to read a self-reported account id). */\nexport function parseJwtClaims(token: string): IdTokenClaims | undefined {\n const parts = token.split(\".\");\n if (parts.length !== 3) return undefined;\n try {\n return JSON.parse(Buffer.from(parts[1] as string, \"base64url\").toString()) as IdTokenClaims;\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort account id from an id/access token's claims (OpenAI/ChatGPT shape). */\nexport function extractAccountId(tokens: {\n id_token?: string;\n access_token?: string;\n}): string | undefined {\n for (const token of [tokens.id_token, tokens.access_token]) {\n if (token === undefined) continue;\n const claims = parseJwtClaims(token);\n const id =\n claims?.chatgpt_account_id ??\n claims?.[\"https://api.openai.com/auth\"]?.chatgpt_account_id ??\n claims?.organizations?.[0]?.id;\n if (id !== undefined) return id;\n }\n return undefined;\n}\n"]}
1
+ {"version":3,"sources":["../../src/internal/auth/oauth-device.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * M42 — the SDK OAuth device-authorization flows (RFC 8628 + OpenAI two-step headless). Split out of\n * oauth-engine.ts to respect the 500-line file budget; a sibling of the core engine. Promoted DOWN from\n * agent-builder's hardened `agents/lib/oauth-device.ts` (M37).\n *\n * Implements two device flows against their published specs: the RFC 8628 device-grant poll loop\n * (device_code → authorization_pending / slow_down) and OpenAI's two-step headless flow, including the\n * JWT-claim / account-id extraction used to attribute an account.\n * Design choices: network I/O + clock + sleep are INJECTED (deterministic tests), failures raise the SDK's\n * typed `AuthCallbackError`, and a device-code expiry DEADLINE guards each poll loop.\n *\n * @internal\n */\nimport { AuthCallbackError } from \"../../server/auth/errors.js\";\nimport { exchangeCode } from \"./oauth-engine.js\";\nimport type {\n DeviceCodeGrant,\n DeviceDeps,\n DeviceOAuthConfig,\n OAuthTokens,\n OpenAIDeviceConfig,\n} from \"./types.js\";\n\n// ─── OAuth 2.0 Device Authorization Grant (RFC 8628) — terminal-first / headless login ───\n\n/** Buffer added to every poll interval to avoid hitting the server a hair early. */\nconst POLLING_SAFETY_MARGIN_MS = 3000;\n\n/**\n * Parse a JSON body, or fail with this module's typed error instead of a raw `SyntaxError`.\n *\n * Every failure in this file is supposed to reach the caller as an `AuthCallbackError` carrying a code\n * the CLI can branch on. `res.json()` breaks that contract: a device endpoint that answers with HTML —\n * a captive portal, a corporate proxy's sign-in page, a load balancer's error page — makes it reject\n * with a `SyntaxError` that no `catch` here handles, so it escapes untyped past a caller prepared only\n * for `AuthCallbackError`.\n *\n * The body is quoted, truncated, because \"not JSON\" and \"not JSON, and it looks like a proxy login\n * page\" are different diagnoses for whoever is holding the terminal — and routing them to the provider\n * when the fault is their network is the expensive kind of wrong.\n */\nasync function parseJsonOrFail(res: Response, code: string, what: string): Promise<unknown> {\n const text = await res.text().catch(() => \"\");\n try {\n return JSON.parse(text);\n } catch {\n throw new AuthCallbackError(code, `${what} returned a non-JSON body: ${text.slice(0, 120)}`);\n }\n}\n\n/** Step 1 — request a device code. POSTs `{client_id, scope}` to the device endpoint. */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: RFC 8628 device-code request — cohesive network response parser ported verbatim (ADR D3).\nexport async function requestDeviceCode(\n config: DeviceOAuthConfig,\n deps: Pick<DeviceDeps, \"fetch\">,\n): Promise<DeviceCodeGrant> {\n let res: Response;\n try {\n res = await deps.fetch(config.deviceCodeEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body: new URLSearchParams({\n client_id: config.clientId,\n scope: config.scopes.join(\" \"),\n }).toString(),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `device request failed: ${(err as Error).message}`,\n );\n }\n if (!res.ok) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `device endpoint returned HTTP ${res.status}`,\n );\n }\n const data = (await parseJsonOrFail(\n res,\n \"oauth_device_authorization_failed\",\n \"device endpoint\",\n )) as {\n device_code?: unknown;\n user_code?: unknown;\n verification_uri?: unknown;\n verification_uri_complete?: unknown;\n interval?: unknown;\n expires_in?: unknown;\n };\n if (typeof data.device_code !== \"string\" || typeof data.user_code !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n \"device response missing device_code/user_code\",\n );\n }\n const verification =\n typeof data.verification_uri === \"string\"\n ? data.verification_uri\n : typeof data.verification_uri_complete === \"string\"\n ? data.verification_uri_complete\n : \"\";\n return {\n deviceCode: data.device_code,\n userCode: data.user_code,\n verificationUri: verification,\n interval: typeof data.interval === \"number\" && data.interval > 0 ? data.interval : 5,\n expiresIn: typeof data.expires_in === \"number\" && data.expires_in > 0 ? data.expires_in : 900,\n };\n}\n\n/**\n * Step 2 — poll the token endpoint until the user approves (or the code expires). Handles RFC 8628\n * `authorization_pending` (keep waiting) and `slow_down` (back off), with an expiry deadline so it cannot\n * spin forever.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: RFC 8628 poll state machine (authorization_pending / slow_down / success / expiry) — a cohesive loop ported verbatim (ADR D3); fragmenting it would obscure the protocol.\nexport async function pollDeviceToken(\n config: DeviceOAuthConfig,\n grant: DeviceCodeGrant,\n deps: DeviceDeps,\n): Promise<OAuthTokens> {\n const deadline = deps.now() + grant.expiresIn * 1000;\n let intervalMs = grant.interval * 1000;\n\n while (deps.now() < deadline) {\n let res: Response;\n try {\n res = await deps.fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n accept: \"application/json\",\n },\n body: new URLSearchParams({\n client_id: config.clientId,\n device_code: grant.deviceCode,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }).toString(),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device token poll failed: ${(err as Error).message}`,\n );\n }\n // RFC 8628 §3.5: `authorization_pending` / `slow_down` arrive as a 400 with an `error` body — so the\n // body MUST be read before deciding failure. Parse first; only a non-OAuth-shaped body on a non-2xx fails.\n const data = (await res.json().catch(() => ({}))) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n id_token?: string;\n error?: string;\n interval?: number;\n };\n if (!res.ok && typeof data.error !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device token endpoint returned HTTP ${res.status}`,\n );\n }\n\n if (typeof data.access_token === \"string\" && data.access_token.length > 0) {\n const expiresIn = typeof data.expires_in === \"number\" ? data.expires_in : 0;\n const accountId = extractAccountId({\n access_token: data.access_token,\n id_token: data.id_token,\n });\n return {\n access: data.access_token,\n // Some device-grant providers (e.g. GitHub) issue no separate refresh token — reuse the access\n // token as the refresh handle.\n refresh: data.refresh_token ?? data.access_token,\n expires: deps.now() + expiresIn * 1000,\n ...(accountId !== undefined ? { accountId } : {}),\n ...(typeof data.id_token === \"string\" && data.id_token.length > 0\n ? { idToken: data.id_token }\n : {}),\n };\n }\n\n if (data.error === \"authorization_pending\") {\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n continue;\n }\n if (data.error === \"slow_down\") {\n // RFC 8628 §3.5: add 5s to the interval, or honor a server-provided one.\n intervalMs =\n (typeof data.interval === \"number\" && data.interval > 0\n ? data.interval\n : grant.interval + 5) * 1000;\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n continue;\n }\n if (typeof data.error === \"string\") {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device authorization rejected: ${data.error}`,\n );\n }\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n }\n throw new AuthCallbackError(\n \"oauth_device_code_expired\",\n \"the device code expired before it was approved\",\n );\n}\n\n/**\n * The full device login: request a code, hand the user the verification URL + user code via `onPrompt`\n * (never printed here — the caller renders it, keeping this module output-free), then poll to completion.\n */\nexport async function deviceLogin(\n config: DeviceOAuthConfig,\n deps: DeviceDeps,\n hooks: {\n onPrompt: (p: { userCode: string; verificationUri: string; expiresIn: number }) => void;\n },\n): Promise<OAuthTokens> {\n const grant = await requestDeviceCode(config, deps);\n hooks.onPrompt({\n userCode: grant.userCode,\n verificationUri: grant.verificationUri,\n expiresIn: grant.expiresIn,\n });\n return pollDeviceToken(config, grant, deps);\n}\n\n// ─── OpenAI / ChatGPT \"headless\" device flow (two-step) ───\n\n/** Step 1 (OpenAI) — request the user code. */\nexport async function requestOpenAIUsercode(\n config: OpenAIDeviceConfig,\n deps: Pick<DeviceDeps, \"fetch\">,\n): Promise<{ deviceAuthId: string; userCode: string; interval: number }> {\n let res: Response;\n try {\n res = await deps.fetch(config.deviceUsercodeEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({ client_id: config.clientId }),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `usercode request failed: ${(err as Error).message}`,\n );\n }\n if (!res.ok) {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n `usercode endpoint returned HTTP ${res.status}`,\n );\n }\n const data = (await parseJsonOrFail(\n res,\n \"oauth_device_authorization_failed\",\n \"usercode endpoint\",\n )) as {\n device_auth_id?: unknown;\n user_code?: unknown;\n interval?: unknown;\n };\n if (typeof data.device_auth_id !== \"string\" || typeof data.user_code !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_device_authorization_failed\",\n \"usercode response missing device_auth_id/user_code\",\n );\n }\n const interval = Math.max(\n typeof data.interval === \"string\" ? parseInt(data.interval, 10) || 5 : 5,\n 1,\n );\n return { deviceAuthId: data.device_auth_id, userCode: data.user_code, interval };\n}\n\n/**\n * Step 2+3 (OpenAI) — poll for the authorization code (200 = ready; 403/404 = pending; else fail), then\n * exchange it for tokens at the standard `/oauth/token` endpoint (reuses `exchangeCode`).\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: OpenAI two-step headless poll loop (200 ready / 403,404 pending / else fail) — cohesive network state machine ported verbatim (ADR D3).\nexport async function openaiDeviceLogin(\n config: OpenAIDeviceConfig,\n deps: DeviceDeps,\n hooks: { onPrompt: (p: { userCode: string; verificationUri: string }) => void },\n): Promise<OAuthTokens> {\n const { deviceAuthId, userCode, interval } = await requestOpenAIUsercode(config, deps);\n hooks.onPrompt({ userCode, verificationUri: config.verificationUri });\n\n const intervalMs = interval * 1000;\n // Bound the loop so it cannot spin forever if the provider never resolves (upstream loops unbounded).\n const deadline = deps.now() + 15 * 60 * 1000;\n while (deps.now() < deadline) {\n let res: Response;\n try {\n res = await deps.fetch(config.devicePollEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({ device_auth_id: deviceAuthId, user_code: userCode }),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device poll failed: ${(err as Error).message}`,\n );\n }\n if (res.ok) {\n const data = (await parseJsonOrFail(res, \"oauth_token_exchange_failed\", \"device poll\")) as {\n authorization_code?: unknown;\n code_verifier?: unknown;\n };\n if (typeof data.authorization_code !== \"string\" || typeof data.code_verifier !== \"string\") {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"device poll returned no authorization_code\",\n );\n }\n const tokens = await exchangeCode(\n config,\n { code: data.authorization_code, verifier: data.code_verifier },\n deps,\n );\n // M43 D4 fix #2 — the two-step exchange returns a JWT access token but `parseTokenResponse` only reads a\n // top-level `account_id`. JWT-extract the `chatgpt_account_id` (as the generic device poll already does)\n // so the login persists a non-empty account_id for the Codex `ChatGPT-Account-Id` header.\n return {\n ...tokens,\n accountId: tokens.accountId ?? extractAccountId({ access_token: tokens.access }),\n };\n }\n // 403/404 = still pending (user has not approved yet); anything else is a hard failure.\n if (res.status !== 403 && res.status !== 404) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `device poll returned HTTP ${res.status}`,\n );\n }\n await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);\n }\n throw new AuthCallbackError(\n \"oauth_device_code_expired\",\n \"the device authorization expired before approval\",\n );\n}\n\n/** JWT id/access-token claims we read to attribute an account. */\ninterface IdTokenClaims {\n chatgpt_account_id?: string;\n organizations?: Array<{ id: string }>;\n \"https://api.openai.com/auth\"?: { chatgpt_account_id?: string };\n}\n\n/** Decode a JWT's claim set (no signature verification — used only to read a self-reported account id). */\nexport function parseJwtClaims(token: string): IdTokenClaims | undefined {\n const parts = token.split(\".\");\n if (parts.length !== 3) return undefined;\n try {\n return JSON.parse(Buffer.from(parts[1] as string, \"base64url\").toString()) as IdTokenClaims;\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort account id from an id/access token's claims (OpenAI/ChatGPT shape). */\nexport function extractAccountId(tokens: {\n id_token?: string;\n access_token?: string;\n}): string | undefined {\n for (const token of [tokens.id_token, tokens.access_token]) {\n if (token === undefined) continue;\n const claims = parseJwtClaims(token);\n const id =\n claims?.chatgpt_account_id ??\n claims?.[\"https://api.openai.com/auth\"]?.chatgpt_account_id ??\n claims?.organizations?.[0]?.id;\n if (id !== undefined) return id;\n }\n return undefined;\n}\n"]}
@@ -65,6 +65,8 @@ function buildFrontmatter(fields, parsed, name) {
65
65
  if (hasContent(fields.category)) result.category = fields.category;
66
66
  const deps = parseDependencies(fields.dependencies);
67
67
  if (deps !== void 0) result.dependencies = deps;
68
+ const bins = parseDependencies(fields["requires-bins"]);
69
+ if (bins !== void 0) result.requiresBins = bins;
68
70
  const disable = readAuthorizationFlag(parsed, "disable-model-invocation", name);
69
71
  if (disable !== void 0) result.disableModelInvocation = disable;
70
72
  const invocable = readAuthorizationFlag(parsed, "user-invocable", name);
@@ -129,6 +131,7 @@ function tryParseSkill(raw, fallbackName, source, options) {
129
131
  };
130
132
  if (frontmatter.category !== void 0) skill.category = frontmatter.category;
131
133
  if (frontmatter.dependencies !== void 0) skill.dependencies = frontmatter.dependencies;
134
+ if (frontmatter.requiresBins !== void 0) skill.requiresBins = frontmatter.requiresBins;
132
135
  if (frontmatter.disableModelInvocation !== void 0) {
133
136
  skill.disableModelInvocation = frontmatter.disableModelInvocation;
134
137
  }
@@ -166,5 +169,5 @@ ${lines.join("\n")}
166
169
  }
167
170
 
168
171
  export { buildSkillsBlock, discoverSkills, escapeBlockBody, loadSkillInstructions, stripSkillFrontmatter };
169
- //# sourceMappingURL=chunk-NHOEXFZT.js.map
170
- //# sourceMappingURL=chunk-NHOEXFZT.js.map
172
+ //# sourceMappingURL=chunk-2HXTNVLM.js.map
173
+ //# sourceMappingURL=chunk-2HXTNVLM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/runtime/skills/skill-frontmatter.ts","../src/internal/runtime/skills/discover-skills.ts","../src/internal/runtime/system-prompt/escape.ts","../src/internal/runtime/skills/skills-block.ts"],"names":[],"mappings":"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","file":"chunk-2HXTNVLM.js","sourcesContent":["import { ConfigurationError } from \"../../../errors.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\n\ntype StringFields = Record<string, string | undefined>;\n\n/** Narrow a FrontmatterValue to string; non-strings + undefined → undefined. */\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\n/** Coerce parser output to legacy string-only shape (skill schema is all-string). */\nfunction toStringFields(raw: Record<string, FrontmatterValue | undefined>): StringFields {\n const out: StringFields = {};\n for (const [k, v] of Object.entries(raw)) out[k] = asString(v);\n return out;\n}\n\n/**\n * Strict skill frontmatter schema (ADR D10).\n *\n * Required: `name`, `description`.\n * Optional: `category`, `dependencies` (comma-separated string in the\n * simple-YAML dialect — parsed to `string[]`).\n *\n * Unknown fields are ignored (forward-compat). Malformed YAML or missing\n * required fields surface as `ConfigurationError` with one of the typed\n * codes below.\n *\n * @internal\n */\nexport interface SkillFrontmatter {\n name: string;\n description: string;\n category?: string;\n dependencies?: string[];\n /**\n * `requires-bins: git, rg` (B-546) — executables the skill needs on PATH. A skill whose binary is\n * absent is not offered to the model; `skills.get(name)` still resolves it.\n */\n requiresBins?: string[];\n /**\n * `disable-model-invocation: true` — the model is not told this skill exists.\n *\n * The SDK owns this boundary: a skill reaches the model through exactly one place,\n * `buildSystemPromptContext`, so the declaration is enforceable here. It is a DISCLOSURE rule,\n * not an execution rule — `skills.get(name)` still resolves it, because a caller naming a skill\n * has already made the decision the field exists to keep away from the model.\n */\n disableModelInvocation?: boolean;\n /**\n * `user-invocable: false` — carried, deliberately not enforced here.\n *\n * This SDK has no user-facing invocation surface for skills; there is no slash command. Reading\n * `agent.skills.list()` as \"the user\" would be a guess — a host may call it to build a picker or\n * to introspect, and the two want opposite answers. So the declaration travels to the host that\n * does know, instead of being discarded (the defect) or enforced against an invented boundary.\n */\n userInvocable?: boolean;\n}\n\n/**\n * Parse a SKILL.md file body into validated frontmatter.\n *\n * @throws ConfigurationError(code: \"missing_frontmatter\") — no `---` block at file head.\n * @throws ConfigurationError(code: \"schema_invalid\") — YAML malformed OR required field missing.\n *\n * @internal\n */\nexport function parseSkillFrontmatter(raw: string, fallbackName: string): SkillFrontmatter {\n const parsed = extractAndParseFrontmatter(raw, fallbackName);\n const fields = toStringFields(parsed);\n const name = resolveName(fields, fallbackName);\n ensureRequiredFields(fields, name);\n return buildFrontmatter(fields, parsed, name);\n}\n\n/**\n * SE20 — return a SKILL.md's BODY (everything after the frontmatter block), trimmed.\n * When there is no frontmatter block, the whole file is the body. Reuses the same\n * frontmatter regex as {@link parseSkillFrontmatter} (DRY).\n */\nexport function stripSkillFrontmatter(raw: string): string {\n const match = /^---\\s*\\n[\\s\\S]*?\\n---\\s*\\n/.exec(raw);\n return (match === null ? raw : raw.slice(match[0].length)).trim();\n}\n\nfunction extractAndParseFrontmatter(\n raw: string,\n fallbackName: string,\n): Record<string, FrontmatterValue | undefined> {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Skill ${fallbackName} is missing frontmatter`, {\n code: \"missing_frontmatter\",\n });\n }\n const frontmatter = match[1] ?? \"\";\n // EC-5: guard against syntactically invalid frontmatter so the loader\n // surfaces schema_invalid rather than crashing.\n try {\n return parseSimpleYaml(frontmatter);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConfigurationError(\n `Skill ${fallbackName} has malformed YAML frontmatter: ${detail}`,\n { code: \"schema_invalid\", cause },\n );\n }\n}\n\nfunction resolveName(fields: StringFields, fallbackName: string): string {\n if (hasContent(fields.name)) return fields.name;\n if (hasContent(fallbackName)) return fallbackName;\n throw new ConfigurationError(\"Skill at unknown path is missing required field: name\", {\n code: \"schema_invalid\",\n });\n}\n\nfunction ensureRequiredFields(fields: StringFields, name: string): void {\n if (!hasContent(fields.description)) {\n throw new ConfigurationError(`Skill ${name} is missing required field: description`, {\n code: \"schema_invalid\",\n });\n }\n}\n\nfunction buildFrontmatter(\n fields: StringFields,\n parsed: Record<string, FrontmatterValue | undefined>,\n name: string,\n): SkillFrontmatter {\n const description = fields.description;\n if (description === undefined) {\n // ensureRequiredFields already threw; this is unreachable but satisfies TS\n throw new ConfigurationError(`Skill ${name} missing description`, { code: \"schema_invalid\" });\n }\n const result: SkillFrontmatter = { name, description };\n if (hasContent(fields.category)) result.category = fields.category;\n const deps = parseDependencies(fields.dependencies);\n if (deps !== undefined) result.dependencies = deps;\n const bins = parseDependencies(fields[\"requires-bins\"]);\n if (bins !== undefined) result.requiresBins = bins;\n const disable = readAuthorizationFlag(parsed, \"disable-model-invocation\", name);\n if (disable !== undefined) result.disableModelInvocation = disable;\n const invocable = readAuthorizationFlag(parsed, \"user-invocable\", name);\n if (invocable !== undefined) result.userInvocable = invocable;\n return result;\n}\n\n/**\n * Read one boolean authorization flag, refusing a value this parser cannot represent.\n *\n * Unknown frontmatter keys are ignored here for forward compatibility, and that is right for\n * metadata. It is wrong for these two: the simple-YAML dialect coerces only the literals `true`\n * and `false`, so `disable-model-invocation: yes` — a valid YAML boolean — arrives as the STRING\n * `\"yes\"`, compares unequal to `true`, and the skill is disclosed to the model. The author wrote a\n * restriction and got the default.\n *\n * A restriction that fails open is worse than one that is absent, because the author stops looking.\n * So the value is refused and named, and the run stops on the line that caused it.\n */\nfunction readAuthorizationFlag(\n parsed: Record<string, FrontmatterValue | undefined>,\n key: string,\n name: string,\n): boolean | undefined {\n const value = parsed[key];\n if (value === undefined) return undefined;\n if (typeof value === \"boolean\") return value;\n throw new ConfigurationError(\n `Skill ${name}: \"${key}\" must be true or false (got ${JSON.stringify(value)}) — ` +\n `this dialect reads only the literals \\`true\\` and \\`false\\`, and a value it cannot read ` +\n `would leave the skill disclosed`,\n { code: \"schema_invalid\" },\n );\n}\n\nfunction parseDependencies(raw: string | undefined): string[] | undefined {\n if (!hasContent(raw)) return undefined;\n const deps = (raw as string)\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return deps.length > 0 ? deps : undefined;\n}\n\nfunction hasContent(value: string | undefined): value is string {\n return value !== undefined && value.trim().length > 0;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport { assertNoSymlinkEscape, safePathJoin } from \"../../security/path-guard.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { parseSkillFrontmatter, stripSkillFrontmatter } from \"./skill-frontmatter.js\";\n\n/**\n * A discovered skill's metadata. The skill BODY is never included — only the\n * strict frontmatter fields plus the resolved `source` path.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport interface Skill {\n name: string;\n description: string;\n /** Absolute path to the discovered `SKILL.md`. */\n source: string;\n category?: string;\n dependencies?: string[];\n /** `requires-bins` — executables that must be on PATH for the model to be offered this skill. */\n requiresBins?: string[];\n /** `disable-model-invocation: true` — the model is never told this skill exists. */\n disableModelInvocation?: boolean;\n /** `user-invocable: false` — carried for a host that has a picker; this SDK has no such surface. */\n userInvocable?: boolean;\n}\n\n/**\n * Information passed to `onInvalidSkill` when a `SKILL.md` is present but its\n * frontmatter is malformed (missing required field or invalid YAML).\n *\n * @public\n */\nexport interface InvalidSkillInfo {\n /** The skill directory name (used as the fallback skill name). */\n name: string;\n /** Absolute path to the offending `SKILL.md`. */\n source: string;\n /** Typed reason: `missing_frontmatter` or `schema_invalid`. */\n code: string;\n message: string;\n}\n\n/**\n * Options for {@link discoverSkills}.\n *\n * @public\n */\nexport interface DiscoverSkillsOptions {\n /**\n * Called once per directory that contains a `SKILL.md` with malformed\n * frontmatter. The skill is excluded from the result; discovery continues\n * (strict-frontmatter ADR / EC-5). A directory WITHOUT a `SKILL.md` is NOT a\n * malformed skill and does not trigger this callback.\n *\n * Default: no-op (a library primitive must not write to the consumer's\n * stderr by default).\n */\n onInvalidSkill?: (info: InvalidSkillInfo) => void;\n}\n\n/**\n * Discover `SKILL.md` skills under an arbitrary directory.\n *\n * For each immediate subdirectory `<dir>/<name>/` containing a `SKILL.md`, the\n * file's strict YAML frontmatter is parsed (`name`/`description` required;\n * `category`/`dependencies` optional). Malformed skills are skipped (optionally\n * reported via {@link DiscoverSkillsOptions.onInvalidSkill}); a subdirectory\n * whose realpath escapes `dir` (via symlink) is skipped (symlink-escape guard,\n * reusing `@theokit/sdk/path-safety`).\n *\n * NEVER throws: a missing, unreadable, or non-directory `dir` yields `[]`.\n *\n * Discovery order follows the filesystem `readdir` order (OS-dependent). Sort\n * the result before {@link buildSkillsBlock} if a stable block order matters.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport async function discoverSkills(\n dir: string,\n options?: DiscoverSkillsOptions,\n): Promise<Skill[]> {\n let entries: Awaited<ReturnType<typeof readWorkspaceDir>>;\n try {\n entries = await readWorkspaceDir(dir, \"skills_read_error\", \"skills directory\");\n } catch {\n // never-throw contract: unreadable / not-a-directory → no skills (EC-1)\n return [];\n }\n\n const skills: Skill[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n let skillDir: string;\n try {\n skillDir = safePathJoin(dir, entry.name);\n assertNoSymlinkEscape(skillDir, dir);\n } catch {\n continue;\n }\n const skillPath = join(skillDir, \"SKILL.md\");\n let raw: string;\n try {\n raw = await readFile(skillPath, \"utf8\");\n } catch {\n // no SKILL.md in this subdir → not a skill, not an error (EC-2)\n continue;\n }\n const skill = tryParseSkill(raw, entry.name, skillPath, options);\n if (skill !== undefined) skills.push(skill);\n }\n return skills;\n}\n\nfunction tryParseSkill(\n raw: string,\n fallbackName: string,\n source: string,\n options: DiscoverSkillsOptions | undefined,\n): Skill | undefined {\n try {\n const frontmatter = parseSkillFrontmatter(raw, fallbackName);\n const skill: Skill = {\n name: frontmatter.name,\n description: frontmatter.description,\n source,\n };\n if (frontmatter.category !== undefined) skill.category = frontmatter.category;\n if (frontmatter.dependencies !== undefined) skill.dependencies = frontmatter.dependencies;\n // The authorization flags travel with the record. Dropping them here would reproduce the defect\n // one layer down: the parser would read the declaration and the discovery result would not\n // carry it, which is indistinguishable from never having parsed it.\n if (frontmatter.requiresBins !== undefined) skill.requiresBins = frontmatter.requiresBins;\n if (frontmatter.disableModelInvocation !== undefined) {\n skill.disableModelInvocation = frontmatter.disableModelInvocation;\n }\n if (frontmatter.userInvocable !== undefined) skill.userInvocable = frontmatter.userInvocable;\n return skill;\n } catch (cause) {\n if (cause instanceof ConfigurationError) {\n options?.onInvalidSkill?.({\n name: fallbackName,\n source,\n code: cause.code ?? \"unknown\",\n message: cause.message,\n });\n return undefined;\n }\n throw cause;\n }\n}\n\n/**\n * Read the BODY of a discovered skill — everything after its frontmatter.\n *\n * A thin selector over {@link discoverSkills} rather than a second reader, which is the same\n * relationship `loadSubagentDefinition` has to `discoverSubagents` in the sibling domain: one\n * parser is the point.\n *\n * ## Why this exists rather than a field on `Skill`\n *\n * `Skill` documents that *\"the skill BODY is never included\"*. That is a written contract with no\n * written reason, and widening it on a guess about the reason is not a trade worth making — a\n * catalog you can put in a prompt without carrying every body is the likely intent, and this keeps\n * that shape intact for whoever relied on it.\n *\n * The body was never expensive to obtain: `discoverSkills` already reads each file in full and\n * discards everything but the frontmatter. What was missing was a door that hands it over.\n *\n * ## What it is for\n *\n * Turning a discovered skill into an inline one — `SkillsSettings.inline` requires `instructions`,\n * and without this the only route was to open `source` and split the frontmatter by hand. That is a\n * second implementation of this module's own convention, and it would fail SILENTLY if the format\n * moved: the frontmatter would land inside the instructions and nothing would say so.\n *\n * Reported by the `theocode` session, which needed exactly that to give an operator's\n * `~/.theokit/skills/` to an agent through the SDK's own parser.\n *\n * @param skill - a record returned by {@link discoverSkills}; its `source` is read.\n * @returns the trimmed body. A file that is all frontmatter yields an empty string.\n * @throws if `source` is unreadable — unlike discovery, which skips what it cannot read, a caller\n * naming ONE skill has asked about that skill and an empty string would answer a question it did\n * not ask.\n * @public\n */\nexport async function loadSkillInstructions(skill: Skill): Promise<string> {\n return stripSkillFrontmatter(await readFile(skill.source, \"utf8\"));\n}\n","/**\n * Block-body XML escape (ADR D9 — prompt-injection defence).\n *\n * Order matters: `&` MUST be escaped first so subsequent `<`/`>` replacements\n * do not double-encode the `&` characters they introduce.\n *\n * @internal\n */\nexport const escapeBlockBody = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n","import { escapeBlockBody } from \"../system-prompt/escape.js\";\n\n/**\n * Render the `<skills>` system-prompt block from a skill list.\n *\n * Input is the structural subset `{ name, description }` — the skill BODY is\n * NOT in the type, so it cannot leak into the prompt. Both fields are passed\n * through `escapeBlockBody` to neutralise prompt-injection vectors hidden in\n * user-controlled SKILL.md frontmatter (injection-escape ADR).\n *\n * Returns `undefined` for an empty list so the caller can omit the block.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport function buildSkillsBlock(\n skills: ReadonlyArray<{ name: string; description: string }>,\n): string | undefined {\n if (skills.length === 0) return undefined;\n const lines = skills.map(\n (skill) => ` - ${escapeBlockBody(skill.name)}: ${escapeBlockBody(skill.description)}`,\n );\n return `<skills>\\n${lines.join(\"\\n\")}\\n</skills>`;\n}\n"]}