@code-yeongyu/senpi 2026.11.10 → 2026.11.11
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.
- package/CHANGELOG.md +95 -0
- package/dist/bundle/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
- package/dist/bundle/chunks/{SKILL-AZ6ONO52.js → SKILL-5O2UKORG.js} +1 -1
- package/dist/bundle/chunks/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
- package/dist/bundle/chunks/{args-77GYOPEO.js → args-HFYB6URH.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-YFZ734T3.js → azure-openai-responses-MNN2TINI.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZUQCYIRY.js → chunk-2ODGHBUD.js} +1 -1
- package/dist/bundle/chunks/chunk-3IYEGHBI.js +2 -0
- package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
- package/dist/bundle/chunks/{chunk-YKYWL2NO.js → chunk-5F2XC6UC.js} +3 -3
- package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
- package/dist/bundle/chunks/{chunk-R3OP7IA4.js → chunk-5PIZWSNG.js} +4 -4
- package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
- package/dist/bundle/chunks/{chunk-Q63YLZMN.js → chunk-BPEA5SWW.js} +2 -2
- package/dist/bundle/chunks/{chunk-MOVJQVMC.js → chunk-BUDJLVMF.js} +3 -3
- package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
- package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
- package/dist/bundle/chunks/{chunk-RCHIMH7N.js → chunk-DQY26AX3.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
- package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
- package/dist/bundle/chunks/{chunk-3Q5ZENSD.js → chunk-E6ZBTSC6.js} +1 -1
- package/dist/bundle/chunks/{chunk-GVPZPVVO.js → chunk-IBDAV2AL.js} +3 -3
- package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
- package/dist/bundle/chunks/{chunk-KU7OCWJF.js → chunk-J4ADZ2OK.js} +1 -1
- package/dist/bundle/chunks/{chunk-T2SMQPRE.js → chunk-K3GAGIPX.js} +1 -1
- package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-L3KVP7VJ.js} +3 -3
- package/dist/bundle/chunks/{chunk-IYSJFQUU.js → chunk-L6DF5G6K.js} +1 -1
- package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
- package/dist/bundle/chunks/chunk-LJFQYCFH.js +2 -0
- package/dist/bundle/chunks/{chunk-J2NGJKXL.js → chunk-LXINMDMX.js} +1 -1
- package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
- package/dist/bundle/chunks/{chunk-2U6HXWXS.js → chunk-NNQ34ZDW.js} +1 -1
- package/dist/bundle/chunks/chunk-NQKCJQHB.js +2 -0
- package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-NRZAJUUO.js} +2 -2
- package/dist/bundle/chunks/{chunk-R2Z7CX77.js → chunk-OSNR3IRD.js} +1 -1
- package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
- package/dist/bundle/chunks/{chunk-FDNM2DAE.js → chunk-PUZDKFRT.js} +164 -164
- package/dist/bundle/chunks/{chunk-BPNSOZ2C.js → chunk-QUVSIW2R.js} +1 -1
- package/dist/bundle/chunks/{chunk-WXYMAUFX.js → chunk-QX7WD3IM.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y4ONXQMR.js → chunk-RGHVPUUX.js} +1 -1
- package/dist/bundle/chunks/{chunk-RYPM5BZQ.js → chunk-S7NAJQB3.js} +1 -1
- package/dist/bundle/chunks/{chunk-2BH54YQR.js → chunk-SANWO37B.js} +1 -1
- package/dist/bundle/chunks/{chunk-YQZNARTX.js → chunk-SS5VTBAV.js} +1 -1
- package/dist/bundle/chunks/{chunk-LT353NSF.js → chunk-T4CUD7RF.js} +1 -1
- package/dist/bundle/chunks/{chunk-J7FW7MED.js → chunk-TCZORL3V.js} +1 -1
- package/dist/bundle/chunks/{chunk-KU72IKV5.js → chunk-VGSWOLIY.js} +1 -1
- package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
- package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
- package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-XUDKT4UH.js} +3 -3
- package/dist/bundle/chunks/{cli-main-BX5AFWJQ.js → cli-main-7II7GCIR.js} +1 -1
- package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-N3B5YJS7.js} +1 -1
- package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{hand-entry-F6PWKNPJ.js → hand-entry-ONOLRMVB.js} +3 -3
- package/dist/bundle/chunks/{headless-host-HKGNJGYT.js → headless-host-53UMJMM6.js} +1 -1
- package/dist/bundle/chunks/{health-write-FBF74QJ7.js → health-write-JLSF5EVV.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-5SCPY3OH.js → help-fast-path-552QLMWT.js} +1 -1
- package/dist/bundle/chunks/{host-command-47QQKJEY.js → host-command-KWMJLPBS.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-VSAH4M3R.js → host-lifecycle-4WQDCN3Y.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +5 -5
- package/dist/bundle/chunks/{host-session-command-SL4AN5WD.js → host-session-command-ZDYEKCUB.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-JDMIHJMQ.js → interactive-mode-EPE4M32G.js} +1 -1
- package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
- package/dist/bundle/chunks/{main-4RITUAQF.js → main-OM7BKVZG.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-SKDD4OWM.js → multi-session-host-DB6XMY53.js} +11 -8
- package/dist/bundle/chunks/openai-codex-responses-VRGOAFEG.js +10 -0
- package/dist/bundle/chunks/{openai-responses-NIVBWE2H.js → openai-responses-I7PXRWCM.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-B5VA4KFO.js → package-manager-cli-GLU5Q2KV.js} +1 -1
- package/dist/bundle/chunks/{role-HQUN4XXK.js → role-AEOLJRTP.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-F3BEMXBE.js → rotation-stream-E3FJFHXJ.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-SDJVBT36.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
- package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-4AENRP7P.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-FPEQCX33.js → schedule-command-5SQD7M5N.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-R25A2QGS.js +3 -0
- package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-GZM5CVQL.js} +1 -1
- package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +335 -332
- package/dist/bundle/chunks/{state-store-S7QAESIF.js → state-store-IECVTYXC.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +5 -5
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli.js +9 -3
- package/dist/core/agent-session-runtime.d.ts +2 -0
- package/dist/core/agent-session-runtime.js +5 -0
- package/dist/core/agent-session.d.ts +15 -3
- package/dist/core/agent-session.js +91 -18
- package/dist/core/assistant-error-redaction.d.ts +9 -0
- package/dist/core/assistant-error-redaction.js +27 -0
- package/dist/core/config-reference-redaction.js +90 -10
- package/dist/core/context-files.d.ts +10 -0
- package/dist/core/context-files.js +58 -12
- package/dist/core/credential-accounts.d.ts +2 -0
- package/dist/core/credential-accounts.js +10 -1
- package/dist/core/dynamic-prompt/intent-gate.js +2 -2
- package/dist/core/dynamic-prompt/style.js +2 -2
- package/dist/core/dynamic-prompt/verification.js +2 -2
- package/dist/core/egress/wire.d.ts +13 -0
- package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-account-test.js +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.d.ts +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.js +47 -16
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.js +4 -2
- package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.d.ts +20 -0
- package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.js +39 -0
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.d.ts +17 -0
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.js +23 -0
- package/dist/core/extensions/builtin/anthropic-subscription/errors.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/errors.js +14 -5
- package/dist/core/extensions/builtin/anthropic-subscription/oauth-login.js +3 -3
- package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.d.ts +10 -4
- package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.js +14 -5
- package/dist/core/extensions/builtin/anthropic-subscription/query-environment.d.ts +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/query-environment.js +5 -4
- package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.d.ts +2 -1
- package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.js +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/stream.js +11 -1
- package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
- package/dist/core/extensions/builtin/goal/continuation.d.ts +6 -0
- package/dist/core/extensions/builtin/goal/continuation.js +3 -2
- package/dist/core/extensions/builtin/goal/index.js +14 -26
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +8 -6
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +77 -39
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +66 -7
- package/dist/core/extensions/builtin/goal/persistence.js +20 -0
- package/dist/core/extensions/builtin/goal/provider-retry.d.ts +9 -0
- package/dist/core/extensions/builtin/goal/provider-retry.js +16 -0
- package/dist/core/extensions/builtin/goal/reload-reengagement.d.ts +3 -2
- package/dist/core/extensions/builtin/goal/reload-reengagement.js +4 -4
- package/dist/core/extensions/builtin/goal/session-start-continuation.d.ts +28 -0
- package/dist/core/extensions/builtin/goal/session-start-continuation.js +75 -0
- package/dist/core/extensions/builtin/goal/store.d.ts +3 -2
- package/dist/core/extensions/builtin/goal/store.js +9 -2
- package/dist/core/extensions/builtin/goal/types.d.ts +9 -0
- package/dist/core/extensions/builtin/handoff-hint/index.js +15 -11
- package/dist/core/extensions/builtin/handoff-hint/replacement-relay.d.ts +16 -0
- package/dist/core/extensions/builtin/handoff-hint/replacement-relay.js +17 -0
- package/dist/core/extensions/builtin/html-render/tool.js +13 -3
- package/dist/core/extensions/builtin/imagegen/skill/SKILL.md +1 -1
- package/dist/core/extensions/builtin/imagegen/tool.js +4 -14
- package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
- package/dist/core/extensions/builtin/mcp/auth/commands-auth.js +2 -1
- package/dist/core/extensions/builtin/mcp/auth/context.js +3 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +7 -1
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +13 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +6 -17
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +67 -88
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
- package/dist/core/extensions/builtin/mcp/auth/registered-redirect.d.ts +19 -0
- package/dist/core/extensions/builtin/mcp/auth/registered-redirect.js +55 -0
- package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +23 -1
- package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
- package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
- package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
- package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
- package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
- package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
- package/dist/core/extensions/builtin/permission-system/parsers.js +1 -0
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +9 -8
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-haiku-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/deepseek-v4-1-flash.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +5 -5
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3-1.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +7 -6
- package/dist/core/extensions/loader.js +24 -4
- package/dist/core/extensions/runner.d.ts +9 -1
- package/dist/core/extensions/runner.js +9 -4
- package/dist/core/extensions/types.d.ts +19 -3
- package/dist/core/hand/hook-advertise.d.ts +41 -0
- package/dist/core/hand/hook-advertise.js +98 -0
- package/dist/core/hand/hook-contract.d.ts +66 -0
- package/dist/core/hand/hook-contract.js +138 -0
- package/dist/core/open-timing.d.ts +2 -0
- package/dist/core/open-timing.js +7 -1
- package/dist/core/resource-loader.js +21 -3
- package/dist/core/retry-fallback/controller-types.d.ts +2 -0
- package/dist/core/retry-fallback/controller.js +9 -2
- package/dist/core/retry-fallback/cooldown.d.ts +7 -0
- package/dist/core/retry-fallback/cooldown.js +13 -1
- package/dist/core/retry-fallback/credential-recovery.d.ts +7 -0
- package/dist/core/retry-fallback/credential-recovery.js +24 -0
- package/dist/core/retry-fallback/log.d.ts +7 -1
- package/dist/core/retry-fallback/log.js +82 -5
- package/dist/core/retry-fallback/model-config-error.js +2 -1
- package/dist/core/retry-fallback/settings.d.ts +8 -5
- package/dist/core/retry-fallback/settings.js +9 -6
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.js +15 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/turn-origin.d.ts +18 -10
- package/dist/core/turn-origin.js +34 -30
- package/dist/core/ultrafast-lanes.d.ts +1 -0
- package/dist/core/ultrafast-lanes.js +4 -2
- package/dist/modes/interactive/components/footer.d.ts +23 -5
- package/dist/modes/interactive/components/footer.js +45 -16
- package/dist/modes/interactive/interactive-mode.js +9 -1
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
- package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
- package/dist/modes/rpc/admit-external-message-command.d.ts +2 -0
- package/dist/modes/rpc/admit-external-message-command.js +5 -1
- package/dist/modes/rpc/compact-admission-validation.js +4 -1
- package/dist/modes/rpc/connection-authority.d.ts +2 -1
- package/dist/modes/rpc/connection-authority.js +11 -9
- package/dist/modes/rpc/connection-handler.d.ts +2 -0
- package/dist/modes/rpc/connection-handler.js +33 -7
- package/dist/modes/rpc/connection-roles.d.ts +7 -0
- package/dist/modes/rpc/connection-roles.js +15 -0
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/delegated-drive-origin.d.ts +6 -6
- package/dist/modes/rpc/delegated-drive-origin.js +7 -8
- package/dist/modes/rpc/delegation-declaration.d.ts +2 -0
- package/dist/modes/rpc/delegation-declaration.js +7 -3
- package/dist/modes/rpc/effect-commands.d.ts +33 -0
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +7 -1
- package/dist/modes/rpc/host-gc-evidence.d.ts +1 -1
- package/dist/modes/rpc/host-gc-evidence.js +9 -3
- package/dist/modes/rpc/host-gc.js +4 -1
- package/dist/modes/rpc/host-observe-request.d.ts +2 -0
- package/dist/modes/rpc/host-observe-request.js +6 -1
- package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
- package/dist/modes/rpc/host-park-ledger.js +110 -0
- package/dist/modes/rpc/lifecycle-delivery.js +4 -1
- package/dist/modes/rpc/loopless-tool-handlers.d.ts +6 -0
- package/dist/modes/rpc/loopless-tool-handlers.js +14 -1
- package/dist/modes/rpc/media-placeholders.js +1 -0
- package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
- package/dist/modes/rpc/method-classes-schema-host.js +24 -21
- package/dist/modes/rpc/method-classes-schema-session.d.ts +20 -0
- package/dist/modes/rpc/method-classes-schema-session.js +6 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +62 -1
- package/dist/modes/rpc/method-classes-schema.js +5 -2
- package/dist/modes/rpc/method-classes.d.ts +4 -0
- package/dist/modes/rpc/method-classes.js +6 -0
- package/dist/modes/rpc/multi-session-host.js +1 -1
- package/dist/modes/rpc/project-file-commands.d.ts +32 -0
- package/dist/modes/rpc/project-file-commands.js +195 -0
- package/dist/modes/rpc/project-file-path.d.ts +34 -0
- package/dist/modes/rpc/project-file-path.js +93 -0
- package/dist/modes/rpc/rpc-error-types.d.ts +9 -1
- package/dist/modes/rpc/rpc-error-types.js +8 -0
- package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
- package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +10 -0
- package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -0
- package/dist/modes/rpc/session-attachment-gate.js +6 -0
- package/dist/modes/rpc/session-binding.d.ts +1 -0
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-command-activity.js +1 -0
- package/dist/modes/rpc/session-connection-context.d.ts +2 -0
- package/dist/modes/rpc/session-connection-context.js +4 -0
- package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
- package/dist/modes/rpc/session-registry-attach.js +15 -11
- package/dist/modes/rpc/session-registry-claims.js +2 -0
- package/dist/modes/rpc/session-registry-open.js +13 -7
- package/dist/modes/rpc/session-registry-opening.js +2 -1
- package/dist/modes/rpc/session-registry-types.d.ts +4 -0
- package/dist/modes/rpc/session-registry.d.ts +3 -0
- package/dist/modes/rpc/session-registry.js +27 -3
- package/dist/modes/rpc/session-router-close.js +1 -0
- package/dist/modes/rpc/session-router-context.d.ts +4 -2
- package/dist/modes/rpc/session-router-context.js +3 -0
- package/dist/modes/rpc/session-router-host-reads.js +1 -0
- package/dist/modes/rpc/session-router-occupancy.js +10 -1
- package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
- package/dist/modes/rpc/session-router-open-result.js +63 -0
- package/dist/modes/rpc/session-router-open.d.ts +3 -0
- package/dist/modes/rpc/session-router-open.js +15 -63
- package/dist/modes/rpc/session-router-park.d.ts +15 -0
- package/dist/modes/rpc/session-router-park.js +176 -0
- package/dist/modes/rpc/session-router-routing.d.ts +1 -1
- package/dist/modes/rpc/session-router-routing.js +7 -0
- package/dist/modes/rpc/session-status-source.js +2 -2
- package/dist/private-fd-startup-fillers.d.ts +2 -2
- package/dist/private-fd-startup-fillers.js +34 -4
- package/dist/senpi +9 -3
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +44 -2
- package/docs/extensions.md +11 -1
- package/docs/rpc.md +106 -2
- package/docs/settings.md +5 -3
- package/docs/skills.md +1 -1
- package/docs/usage.md +1 -1
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-436DNN4Z.js +0 -2
- package/dist/bundle/chunks/chunk-DLAY7CIU.js +0 -2
- package/dist/bundle/chunks/chunk-SJI6UB23.js +0 -2
- package/dist/bundle/chunks/chunk-XH2MWTXA.js +0 -11
- package/dist/bundle/chunks/chunk-YK3ZZCHI.js +0 -2
- package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
- package/dist/bundle/chunks/rpc-mode-ZDUGW2IM.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-MEDV66PS.js +0 -3
|
@@ -866,6 +866,8 @@ export interface ToolPermissionRequest {
|
|
|
866
866
|
readonly patterns: readonly string[];
|
|
867
867
|
/** Patterns an "always" approval of this call records. */
|
|
868
868
|
readonly always: readonly string[];
|
|
869
|
+
/** An `ask` decision for this call is approved without a prompt; an `allow` or `deny` rule still decides first. */
|
|
870
|
+
readonly autoApproveAsk?: true;
|
|
869
871
|
}
|
|
870
872
|
/**
|
|
871
873
|
* Hints about what a tool does, with the meaning of MCP tool annotations. They come from the tool's
|
|
@@ -1471,6 +1473,18 @@ export interface HandlerRegistrationOptions {
|
|
|
1471
1473
|
*/
|
|
1472
1474
|
runs?: ExtensionSide;
|
|
1473
1475
|
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Registration options for the provider request and stream hooks (`before_provider_request`, `provider_stream_event`,
|
|
1478
|
+
* `after_provider_response`).
|
|
1479
|
+
*/
|
|
1480
|
+
export interface ProviderHookOptions extends HandlerRegistrationOptions {
|
|
1481
|
+
/**
|
|
1482
|
+
* The providers whose requests or streams this handler reads or changes; omitted, every provider. A hand advertises
|
|
1483
|
+
* these to a hosted brain so it knows which calls need the device's hooks (hosted model-key custody CK21). The
|
|
1484
|
+
* declaration never filters dispatch: the handler still receives every event wherever it runs.
|
|
1485
|
+
*/
|
|
1486
|
+
providers?: readonly ProviderId[];
|
|
1487
|
+
}
|
|
1474
1488
|
/** Registration options for `pi.on("before_agent_start", handler, options)`. */
|
|
1475
1489
|
export interface BeforeAgentStartHandlerOptions extends HandlerRegistrationOptions {
|
|
1476
1490
|
/**
|
|
@@ -2155,10 +2169,10 @@ export interface ExtensionAPI {
|
|
|
2155
2169
|
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>, options?: HandlerRegistrationOptions): () => void;
|
|
2156
2170
|
on(event: "context", handler: ExtensionHandler<ContextEvent, ContextEventResult>, options?: ContextHandlerOptions): () => void;
|
|
2157
2171
|
on(event: "context_with_system", handler: ExtensionHandler<ContextWithSystemEvent, ContextEventResult>, options?: ContextHandlerOptions): () => void;
|
|
2158
|
-
on(event: "before_provider_request", handler: ExtensionHandler<BeforeProviderRequestEvent, BeforeProviderRequestEventResult>, options?:
|
|
2172
|
+
on(event: "before_provider_request", handler: ExtensionHandler<BeforeProviderRequestEvent, BeforeProviderRequestEventResult>, options?: ProviderHookOptions): () => void;
|
|
2159
2173
|
on(event: "before_provider_headers", handler: ExtensionHandler<BeforeProviderHeadersEvent>, options?: HandlerRegistrationOptions): () => void;
|
|
2160
|
-
on(event: "after_provider_response", handler: ExtensionHandler<AfterProviderResponseEvent>, options?:
|
|
2161
|
-
on(event: "provider_stream_event", handler: ExtensionHandler<ProviderStreamEvent>, options?:
|
|
2174
|
+
on(event: "after_provider_response", handler: ExtensionHandler<AfterProviderResponseEvent>, options?: ProviderHookOptions): () => void;
|
|
2175
|
+
on(event: "provider_stream_event", handler: ExtensionHandler<ProviderStreamEvent>, options?: ProviderHookOptions): () => void;
|
|
2162
2176
|
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>, options?: BeforeAgentStartHandlerOptions): () => void;
|
|
2163
2177
|
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>, options?: HandlerRegistrationOptions): () => void;
|
|
2164
2178
|
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>, options?: HandlerRegistrationOptions): () => void;
|
|
@@ -3055,6 +3069,8 @@ export interface Extension {
|
|
|
3055
3069
|
nonMutatingContextHandlers?: WeakSet<HandlerFn>;
|
|
3056
3070
|
/** The declared side of each handler registered with `{ runs }`; a handler that declared none has no entry. */
|
|
3057
3071
|
handlerSides?: WeakMap<HandlerFn, ExtensionSide>;
|
|
3072
|
+
/** The declared providers of each provider hook registered with `{ providers }`; undeclared hooks have no entry. */
|
|
3073
|
+
handlerProviders?: WeakMap<HandlerFn, readonly ProviderId[]>;
|
|
3058
3074
|
tools: Map<string, RegisteredTool>;
|
|
3059
3075
|
/** Optional for compatibility with extension records created before child modules. */
|
|
3060
3076
|
childModules?: Map<string, RegisteredChildModule>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ProviderId } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { HandSessionAdvertisement, HookModelRequest, HookModelRequestReply } from "../egress/wire.ts";
|
|
3
|
+
import type { Extension } from "../extensions/types.ts";
|
|
4
|
+
export type HookProviders = readonly ProviderId[] | "*";
|
|
5
|
+
export interface HandHookAccess {
|
|
6
|
+
readonly requestHooks: HookProviders;
|
|
7
|
+
readonly streamHooks: HookProviders;
|
|
8
|
+
}
|
|
9
|
+
/** The request and stream hooks of the extensions `only` accepts (the ones that run on this hand). */
|
|
10
|
+
export declare function handHookAccess(extensions: readonly Extension[], only: (extensionPath: string) => boolean): HandHookAccess;
|
|
11
|
+
export interface AdvertHashParts {
|
|
12
|
+
readonly extensionSetHash: string;
|
|
13
|
+
readonly cwd: string;
|
|
14
|
+
/** The project-local hook set, as hook identities; its order does not matter. */
|
|
15
|
+
readonly projectHooks: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/** `H(extension set hash, cwd, project-local hook set)` (4.9): `sha256:` + hex SHA-256 of their canonical JSON. */
|
|
18
|
+
export declare function computeAdvertHash(parts: AdvertHashParts): string;
|
|
19
|
+
/** A bound session's advertisement, before a connection stamps it. */
|
|
20
|
+
export type HandHookSession = Omit<HandSessionAdvertisement, "t" | "connectionId">;
|
|
21
|
+
/** A hook request for a session not advertised on the connection it names; nothing ran. */
|
|
22
|
+
export declare class HookNotAdvertisedError extends Error {
|
|
23
|
+
readonly name = "HookNotAdvertisedError";
|
|
24
|
+
}
|
|
25
|
+
export interface HandHookLinkOptions {
|
|
26
|
+
send(message: HandSessionAdvertisement | HookModelRequestReply): Promise<void>;
|
|
27
|
+
serve(request: HookModelRequest): Promise<HookModelRequestReply>;
|
|
28
|
+
}
|
|
29
|
+
export interface HandHookLink {
|
|
30
|
+
/** A new connection: every bound session's advertisement goes out first, in order, under `connectionId`. */
|
|
31
|
+
connect(connectionId: string, sessions: readonly HandHookSession[]): Promise<void>;
|
|
32
|
+
/** A session bound (or re-hashed) mid-connection: its advertisement goes out before anything else for it. */
|
|
33
|
+
advertise(session: HandHookSession): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* One `hook.model_request` received on `connectionId`; its reply follows that session's advertisement. Its hooks run
|
|
36
|
+
* outside the outgoing order, so a slow hook holds back no other request's reply and no advertisement.
|
|
37
|
+
*/
|
|
38
|
+
receive(connectionId: string, request: HookModelRequest): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare function createHandHookLink(options: HandHookLinkOptions): HandHookLink;
|
|
41
|
+
//# sourceMappingURL=hook-advertise.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hand's hook advertisement of hosted model-key custody (row CK21; custody design 4.9, round-10 fold section 15).
|
|
3
|
+
*
|
|
4
|
+
* A hand tells a hosted brain, per bound session, which providers' calls need the device's hooks: the request hooks
|
|
5
|
+
* (`before_provider_request`) and the stream hooks (`provider_stream_event`, `after_provider_response`) of the user's
|
|
6
|
+
* extensions, each scoped by its `{ providers }` declaration or, undeclared, every provider (`"*"`). The advertisement
|
|
7
|
+
* carries the `connectionId`, the session's `cwd` and its `advertHash`, and it is the first message for its session on
|
|
8
|
+
* every (re)connect: the link serves no `hook.model_request` that its session's advertisement on that connection did
|
|
9
|
+
* not precede.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { canonicalJson } from "@earendil-works/pi-agent-core";
|
|
13
|
+
const REQUEST_HOOK_EVENTS = ["before_provider_request"];
|
|
14
|
+
const STREAM_HOOK_EVENTS = ["provider_stream_event", "after_provider_response"];
|
|
15
|
+
function hookProviders(extensions, events, only) {
|
|
16
|
+
const declared = new Set();
|
|
17
|
+
for (const extension of extensions) {
|
|
18
|
+
if (!only(extension.path))
|
|
19
|
+
continue;
|
|
20
|
+
for (const event of events)
|
|
21
|
+
for (const handler of extension.handlers.get(event) ?? []) {
|
|
22
|
+
const providers = extension.handlerProviders?.get(handler);
|
|
23
|
+
if (providers === undefined)
|
|
24
|
+
return "*";
|
|
25
|
+
for (const provider of providers)
|
|
26
|
+
declared.add(provider);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...declared].sort();
|
|
30
|
+
}
|
|
31
|
+
/** The request and stream hooks of the extensions `only` accepts (the ones that run on this hand). */
|
|
32
|
+
export function handHookAccess(extensions, only) {
|
|
33
|
+
return {
|
|
34
|
+
requestHooks: hookProviders(extensions, REQUEST_HOOK_EVENTS, only),
|
|
35
|
+
streamHooks: hookProviders(extensions, STREAM_HOOK_EVENTS, only),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** `H(extension set hash, cwd, project-local hook set)` (4.9): `sha256:` + hex SHA-256 of their canonical JSON. */
|
|
39
|
+
export function computeAdvertHash(parts) {
|
|
40
|
+
const canonical = canonicalJson({
|
|
41
|
+
cwd: parts.cwd,
|
|
42
|
+
extensionSetHash: parts.extensionSetHash,
|
|
43
|
+
projectHooks: [...parts.projectHooks].sort(),
|
|
44
|
+
});
|
|
45
|
+
return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
|
|
46
|
+
}
|
|
47
|
+
/** A hook request for a session not advertised on the connection it names; nothing ran. */
|
|
48
|
+
export class HookNotAdvertisedError extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
this.name = "HookNotAdvertisedError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function createHandHookLink(options) {
|
|
55
|
+
let connectionId;
|
|
56
|
+
let advertised = new Set();
|
|
57
|
+
// One outgoing order: an advertisement queued before a reply is sent before it.
|
|
58
|
+
let outgoing = Promise.resolve();
|
|
59
|
+
const enqueue = (work) => {
|
|
60
|
+
const run = outgoing.then(work);
|
|
61
|
+
outgoing = run.then(() => undefined, () => undefined);
|
|
62
|
+
return run;
|
|
63
|
+
};
|
|
64
|
+
const sendAdvert = (connection, session, into) => enqueue(async () => {
|
|
65
|
+
if (connectionId !== connection)
|
|
66
|
+
return;
|
|
67
|
+
await options.send({ t: "hand.session.advertise", connectionId: connection, ...session });
|
|
68
|
+
into.add(session.sessionId);
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
async connect(next, sessions) {
|
|
72
|
+
connectionId = next;
|
|
73
|
+
const fresh = new Set();
|
|
74
|
+
advertised = fresh;
|
|
75
|
+
await Promise.all(sessions.map((session) => sendAdvert(next, session, fresh)));
|
|
76
|
+
},
|
|
77
|
+
async advertise(session) {
|
|
78
|
+
if (connectionId === undefined)
|
|
79
|
+
throw new HookNotAdvertisedError("no connection to advertise on");
|
|
80
|
+
await sendAdvert(connectionId, session, advertised);
|
|
81
|
+
},
|
|
82
|
+
async receive(connection, request) {
|
|
83
|
+
// Only the advertised check and the reply's send take a place in the outgoing order.
|
|
84
|
+
await enqueue(async () => {
|
|
85
|
+
if (connection !== connectionId || !advertised.has(request.sessionId))
|
|
86
|
+
throw new HookNotAdvertisedError(`hook.model_request for session ${JSON.stringify(request.sessionId)} was not preceded by its advertisement on connection ${JSON.stringify(connection)}`);
|
|
87
|
+
});
|
|
88
|
+
const reply = await options.serve(request);
|
|
89
|
+
await enqueue(async () => {
|
|
90
|
+
// A reconnect since the request arrived: its connection is gone, so its reply is not sent.
|
|
91
|
+
if (connection !== connectionId)
|
|
92
|
+
return;
|
|
93
|
+
await options.send(reply);
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=hook-advertise.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request-hook contract of hosted model-key custody (row CK21; custody design 4.9, round-10 fold section 15).
|
|
3
|
+
*
|
|
4
|
+
* `HookRequestInput` is the serializable projection of the fused `before_provider_request` input: the provider payload
|
|
5
|
+
* and the effective request model, without the request headers or the model's own headers, which can carry a
|
|
6
|
+
* credential and never leave the side that dials. A step call has this input; an aux call has none. A hand serves
|
|
7
|
+
* `hook.model_request` by running its user's request hooks on the input, only after the request's placement claim
|
|
8
|
+
* verified, and replies with the payload they produced, with their failures and the messages and notices they sent.
|
|
9
|
+
*/
|
|
10
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
11
|
+
import type { ExecutionScope } from "@earendil-works/pi-agent-core";
|
|
12
|
+
import { type ClaimVerdict } from "../egress/placement-claim.ts";
|
|
13
|
+
import type { HookModelRequest, HookModelRequestReply, ProviderCallErrorCode, ProviderCallKind, WireJson } from "../egress/wire.ts";
|
|
14
|
+
import type { BeforeProviderRequestEvent } from "../extensions/types.ts";
|
|
15
|
+
/** The pinned shape of `HookModelRequestInput` (and of the reply's `output`). */
|
|
16
|
+
export type HookRequestInput = {
|
|
17
|
+
readonly payload: WireJson;
|
|
18
|
+
readonly model?: {
|
|
19
|
+
readonly [key: string]: WireJson;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/** The input or output cannot cross the wire: it is not JSON, or not the closed envelope. */
|
|
23
|
+
export declare class HookContractError extends Error {
|
|
24
|
+
readonly name = "HookContractError";
|
|
25
|
+
}
|
|
26
|
+
/** The request's placement claim did not verify, so no hook ran; `code`/`retryable` are the claim's wire answer. */
|
|
27
|
+
export declare class HookRequestRefusedError extends Error {
|
|
28
|
+
readonly name = "HookRequestRefusedError";
|
|
29
|
+
readonly verdict: Exclude<ClaimVerdict, "valid">;
|
|
30
|
+
readonly code: ProviderCallErrorCode;
|
|
31
|
+
readonly retryable: boolean;
|
|
32
|
+
constructor(verdict: Exclude<ClaimVerdict, "valid">);
|
|
33
|
+
}
|
|
34
|
+
/** The projection of a fused `before_provider_request` input. */
|
|
35
|
+
export declare function hookRequestInputOf(event: Omit<BeforeProviderRequestEvent, "type"> & {
|
|
36
|
+
type?: string;
|
|
37
|
+
}): HookRequestInput;
|
|
38
|
+
/** A step call's hook input; an aux call runs no request hooks on the hand and has none. */
|
|
39
|
+
export declare function hookRequestInputForCall(call: ProviderCallKind, event: Omit<BeforeProviderRequestEvent, "type"> & {
|
|
40
|
+
type?: string;
|
|
41
|
+
}): HookRequestInput | undefined;
|
|
42
|
+
/** The reply's `output`: the payload the hooks produced, with the model the request carried. */
|
|
43
|
+
export declare function hookRequestOutputOf(input: HookRequestInput, payload: unknown): HookRequestInput;
|
|
44
|
+
/** Reads a wire value back as the closed envelope `{ payload, model? }`; the model never carries headers. */
|
|
45
|
+
export declare function parseHookRequestInput(value: unknown): HookRequestInput;
|
|
46
|
+
/** The effective model a hand hands its hooks, as the loop sent it. */
|
|
47
|
+
export declare function hookModelOf(input: HookRequestInput): Model<Api> | undefined;
|
|
48
|
+
/** What the user's request hooks produced: the reply's `output`, their failures and what they sent. */
|
|
49
|
+
export type HookRequestHooksResult = Omit<HookModelRequestReply, "t" | "sessionId" | "stepId" | "output"> & {
|
|
50
|
+
readonly output: HookRequestInput;
|
|
51
|
+
};
|
|
52
|
+
export interface HookServePorts {
|
|
53
|
+
/** The hand's placement-claim check (CK7's verifier with the hand's trust and clock). */
|
|
54
|
+
verifyClaim(claim: unknown): Promise<ClaimVerdict>;
|
|
55
|
+
/** Runs the user's request hooks of `sessionId` on `input` and returns what they produced. */
|
|
56
|
+
runRequestHooks(sessionId: string, input: HookRequestInput): Promise<HookRequestHooksResult>;
|
|
57
|
+
}
|
|
58
|
+
/** Serves one `hook.model_request`: claim first, then the closed input, then the hooks. */
|
|
59
|
+
export declare function serveHookModelRequest(request: HookModelRequest, ports: HookServePorts): Promise<HookModelRequestReply>;
|
|
60
|
+
/**
|
|
61
|
+
* `runRequestHooks` over a hand's loop-hook runner (`LooplessHand.loopHooks`): the session's hooks run on the runtime of
|
|
62
|
+
* `scope`, inside a loop-hook call bound to that session, with the loop busy (a model request is mid-turn). The hooks'
|
|
63
|
+
* failures and what they sent travel with the output, as they do on every loop-hook reply.
|
|
64
|
+
*/
|
|
65
|
+
export declare function loopHookRequestHooks(loopHooks: (event: string, payload: WireJson) => Promise<unknown>, scope: ExecutionScope): HookServePorts["runRequestHooks"];
|
|
66
|
+
//# sourceMappingURL=hook-contract.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { CLAIM_VERDICT_ERRORS } from "../egress/placement-claim.js";
|
|
2
|
+
/** The input or output cannot cross the wire: it is not JSON, or not the closed envelope. */
|
|
3
|
+
export class HookContractError extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(...arguments);
|
|
6
|
+
this.name = "HookContractError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** The request's placement claim did not verify, so no hook ran; `code`/`retryable` are the claim's wire answer. */
|
|
10
|
+
export class HookRequestRefusedError extends Error {
|
|
11
|
+
constructor(verdict) {
|
|
12
|
+
super(`hook.model_request refused: placement claim ${verdict}`);
|
|
13
|
+
this.name = "HookRequestRefusedError";
|
|
14
|
+
this.verdict = verdict;
|
|
15
|
+
this.code = CLAIM_VERDICT_ERRORS[verdict].code;
|
|
16
|
+
this.retryable = CLAIM_VERDICT_ERRORS[verdict].retryable;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const isPlainObject = (value) => {
|
|
20
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
21
|
+
return false;
|
|
22
|
+
const prototype = Object.getPrototypeOf(value);
|
|
23
|
+
return prototype === Object.prototype || prototype === null;
|
|
24
|
+
};
|
|
25
|
+
/** `value` as JSON, exactly; anything JSON would drop, coerce or reject is refused (an `undefined` member is absent). */
|
|
26
|
+
function wireJson(value, at) {
|
|
27
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
28
|
+
return value;
|
|
29
|
+
if (typeof value === "number") {
|
|
30
|
+
if (Number.isFinite(value))
|
|
31
|
+
return value;
|
|
32
|
+
throw new HookContractError(`${at} is not a finite number`);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value))
|
|
35
|
+
return value.map((item, index) => wireJson(item, `${at}[${index}]`));
|
|
36
|
+
if (isPlainObject(value)) {
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const [key, member] of Object.entries(value))
|
|
39
|
+
if (member !== undefined)
|
|
40
|
+
out[key] = wireJson(member, `${at}.${key}`);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
throw new HookContractError(`${at} is not JSON`);
|
|
44
|
+
}
|
|
45
|
+
function wireModel(model) {
|
|
46
|
+
const { headers: _headers, ...rest } = model;
|
|
47
|
+
return wireJson(rest, "model");
|
|
48
|
+
}
|
|
49
|
+
/** The projection of a fused `before_provider_request` input. */
|
|
50
|
+
export function hookRequestInputOf(event) {
|
|
51
|
+
const payload = wireJson(event.payload, "payload");
|
|
52
|
+
return event.model === undefined ? { payload } : { payload, model: wireModel(event.model) };
|
|
53
|
+
}
|
|
54
|
+
/** A step call's hook input; an aux call runs no request hooks on the hand and has none. */
|
|
55
|
+
export function hookRequestInputForCall(call, event) {
|
|
56
|
+
return call.kind === "step" ? hookRequestInputOf(event) : undefined;
|
|
57
|
+
}
|
|
58
|
+
/** The reply's `output`: the payload the hooks produced, with the model the request carried. */
|
|
59
|
+
export function hookRequestOutputOf(input, payload) {
|
|
60
|
+
const out = wireJson(payload, "payload");
|
|
61
|
+
return input.model === undefined ? { payload: out } : { payload: out, model: input.model };
|
|
62
|
+
}
|
|
63
|
+
const MODEL_STRING_FIELDS = ["id", "name", "api", "provider", "baseUrl"];
|
|
64
|
+
/** Reads a wire value back as the closed envelope `{ payload, model? }`; the model never carries headers. */
|
|
65
|
+
export function parseHookRequestInput(value) {
|
|
66
|
+
if (!isPlainObject(value) || !("payload" in value))
|
|
67
|
+
throw new HookContractError("hook input is not an object with a payload");
|
|
68
|
+
const extra = Object.keys(value).filter((key) => key !== "payload" && key !== "model");
|
|
69
|
+
if (extra.length > 0)
|
|
70
|
+
throw new HookContractError(`hook input has unknown members: ${extra.join(", ")}`);
|
|
71
|
+
const payload = wireJson(value.payload, "payload");
|
|
72
|
+
if (value.model === undefined)
|
|
73
|
+
return { payload };
|
|
74
|
+
const model = value.model;
|
|
75
|
+
if (!isPlainObject(model) || MODEL_STRING_FIELDS.some((field) => typeof model[field] !== "string"))
|
|
76
|
+
throw new HookContractError("hook input model is not a model");
|
|
77
|
+
if ("headers" in model)
|
|
78
|
+
throw new HookContractError("hook input model carries headers");
|
|
79
|
+
return { payload, model: wireJson(model, "model") };
|
|
80
|
+
}
|
|
81
|
+
/** The effective model a hand hands its hooks, as the loop sent it. */
|
|
82
|
+
export function hookModelOf(input) {
|
|
83
|
+
return input.model;
|
|
84
|
+
}
|
|
85
|
+
/** Serves one `hook.model_request`: claim first, then the closed input, then the hooks. */
|
|
86
|
+
export async function serveHookModelRequest(request, ports) {
|
|
87
|
+
const verdict = await ports.verifyClaim(request.claim);
|
|
88
|
+
if (verdict !== "valid")
|
|
89
|
+
throw new HookRequestRefusedError(verdict);
|
|
90
|
+
const input = parseHookRequestInput(request.input);
|
|
91
|
+
const produced = await ports.runRequestHooks(request.sessionId, input);
|
|
92
|
+
return { t: "hook.model_request.reply", sessionId: request.sessionId, stepId: request.stepId, ...produced };
|
|
93
|
+
}
|
|
94
|
+
const isHookFailure = (value) => isPlainObject(value) &&
|
|
95
|
+
typeof value.extensionPath === "string" &&
|
|
96
|
+
typeof value.event === "string" &&
|
|
97
|
+
typeof value.error === "string";
|
|
98
|
+
function hookFailures(value) {
|
|
99
|
+
if (!Array.isArray(value) || !value.every(isHookFailure))
|
|
100
|
+
throw new HookContractError("the hand's request-hook reply has malformed errors");
|
|
101
|
+
return value.map(({ extensionPath, event, error }) => ({ extensionPath, event, error }));
|
|
102
|
+
}
|
|
103
|
+
function wireList(value, at) {
|
|
104
|
+
if (!Array.isArray(value))
|
|
105
|
+
throw new HookContractError(`the hand's request-hook reply has malformed ${at}`);
|
|
106
|
+
return value.map((item, index) => wireJson(item, `${at}[${index}]`));
|
|
107
|
+
}
|
|
108
|
+
function droppedCount(value) {
|
|
109
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0)
|
|
110
|
+
return value;
|
|
111
|
+
throw new HookContractError("the hand's request-hook reply has a malformed droppedLate");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `runRequestHooks` over a hand's loop-hook runner (`LooplessHand.loopHooks`): the session's hooks run on the runtime of
|
|
115
|
+
* `scope`, inside a loop-hook call bound to that session, with the loop busy (a model request is mid-turn). The hooks'
|
|
116
|
+
* failures and what they sent travel with the output, as they do on every loop-hook reply.
|
|
117
|
+
*/
|
|
118
|
+
export function loopHookRequestHooks(loopHooks, scope) {
|
|
119
|
+
return async (sessionId, input) => {
|
|
120
|
+
const reply = await loopHooks("before_provider_request", {
|
|
121
|
+
scope: { id: scope.id, root: scope.root },
|
|
122
|
+
sessionId,
|
|
123
|
+
idle: false,
|
|
124
|
+
input,
|
|
125
|
+
});
|
|
126
|
+
if (!isPlainObject(reply))
|
|
127
|
+
throw new HookContractError("the hand's request-hook reply is not an object");
|
|
128
|
+
const { errors, sent, notices, droppedLate } = reply;
|
|
129
|
+
return {
|
|
130
|
+
output: parseHookRequestInput(reply.output),
|
|
131
|
+
...(errors === undefined ? {} : { errors: hookFailures(errors) }),
|
|
132
|
+
...(sent === undefined ? {} : { sent: wireList(sent, "sent") }),
|
|
133
|
+
...(notices === undefined ? {} : { notices: wireList(notices, "notices") }),
|
|
134
|
+
...(droppedLate === undefined ? {} : { droppedLate: droppedCount(droppedLate) }),
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=hook-contract.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface OpenTimer {
|
|
2
2
|
mark(phase: string): void;
|
|
3
3
|
}
|
|
4
|
+
/** Speculative builds have no caller to charge; drop their timing rather than report a misleading open. */
|
|
5
|
+
export declare function withoutOpenTiming<T>(build: () => T): T;
|
|
4
6
|
export declare function openTimingEnabled(): boolean;
|
|
5
7
|
export declare function startOpenTimer(scope: string): OpenTimer;
|
|
6
8
|
//# sourceMappingURL=open-timing.d.ts.map
|
package/dist/core/open-timing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { envValue } from "./brand.js";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
/**
|
|
3
4
|
* Per-phase timing of a session open (senpi#5707), for naming the await that holds a slow open.
|
|
4
5
|
* Enable with SENPI_OPEN_TIMING=1. Off, every call returns before reading a clock or touching
|
|
@@ -11,11 +12,16 @@ import { envValue } from "./brand.js";
|
|
|
11
12
|
*/
|
|
12
13
|
const ENABLED = envValue("OPEN_TIMING") === "1";
|
|
13
14
|
const DISABLED = { mark: () => { } };
|
|
15
|
+
const speculative = new AsyncLocalStorage();
|
|
16
|
+
/** Speculative builds have no caller to charge; drop their timing rather than report a misleading open. */
|
|
17
|
+
export function withoutOpenTiming(build) {
|
|
18
|
+
return speculative.run(true, build);
|
|
19
|
+
}
|
|
14
20
|
export function openTimingEnabled() {
|
|
15
21
|
return ENABLED;
|
|
16
22
|
}
|
|
17
23
|
export function startOpenTimer(scope) {
|
|
18
|
-
if (!ENABLED)
|
|
24
|
+
if (!ENABLED || speculative.getStore())
|
|
19
25
|
return DISABLED;
|
|
20
26
|
let last = performance.now();
|
|
21
27
|
return {
|
|
@@ -167,6 +167,21 @@ function getGlobalDefaultExtensionModulePath(extensionId) {
|
|
|
167
167
|
}
|
|
168
168
|
return canonicalizeGlobalDefaultExtensionModulePath(join(packageDir, sourceRoot, "core", "extensions", "builtin", extensionFile));
|
|
169
169
|
}
|
|
170
|
+
// A write-denied agent dir (a sandboxed review child, senpi#6287) is left as it is: a missing default
|
|
171
|
+
// extension shim is not seeded, so that default does not load, and a stale shim keeps loading whatever it
|
|
172
|
+
// points at. Startup survives either way; any other error is a real failure.
|
|
173
|
+
function seedWritable(write) {
|
|
174
|
+
try {
|
|
175
|
+
write();
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
const code = error.code;
|
|
180
|
+
if (code === "EROFS" || code === "EACCES" || code === "EPERM")
|
|
181
|
+
return false;
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
170
185
|
function buildGlobalDefaultExtensionShim(modulePath) {
|
|
171
186
|
return `${GENERATED_GLOBAL_EXTENSION_BANNER}export { default } from ${JSON.stringify(pathToFileURL(modulePath).href)};\n`;
|
|
172
187
|
}
|
|
@@ -569,6 +584,8 @@ export class DefaultResourceLoader {
|
|
|
569
584
|
cwd: this.cwd,
|
|
570
585
|
agentDir: this.agentDir,
|
|
571
586
|
...this.settingsManager.getSettings().contextFiles,
|
|
587
|
+
// senpi#5567: user settings only; a project's settings never redirect the global file.
|
|
588
|
+
globalPath: this.settingsManager.getGlobalContextFilePath(),
|
|
572
589
|
}),
|
|
573
590
|
};
|
|
574
591
|
const resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;
|
|
@@ -595,7 +612,8 @@ export class DefaultResourceLoader {
|
|
|
595
612
|
return;
|
|
596
613
|
}
|
|
597
614
|
const extensionsDir = join(this.agentDir, "extensions");
|
|
598
|
-
mkdirSync(extensionsDir, { recursive: true })
|
|
615
|
+
if (!seedWritable(() => mkdirSync(extensionsDir, { recursive: true })))
|
|
616
|
+
return;
|
|
599
617
|
for (const extensionId of globalDefaultExtensionIds) {
|
|
600
618
|
const modulePath = getGlobalDefaultExtensionModulePath(extensionId);
|
|
601
619
|
const targetPath = getGlobalDefaultExtensionShimPath(this.agentDir, extensionId);
|
|
@@ -611,10 +629,10 @@ export class DefaultResourceLoader {
|
|
|
611
629
|
if (existing === shim || !isGeneratedGlobalDefaultExtensionShim(existing)) {
|
|
612
630
|
continue;
|
|
613
631
|
}
|
|
614
|
-
writeFileSync(targetPath, shim, "utf-8");
|
|
632
|
+
seedWritable(() => writeFileSync(targetPath, shim, "utf-8"));
|
|
615
633
|
continue;
|
|
616
634
|
}
|
|
617
|
-
writeFileSync(targetPath, shim, "utf-8");
|
|
635
|
+
seedWritable(() => writeFileSync(targetPath, shim, "utf-8"));
|
|
618
636
|
}
|
|
619
637
|
}
|
|
620
638
|
resolvePackagePaths(options = {}) {
|
|
@@ -16,6 +16,8 @@ export interface ActiveFallbackState {
|
|
|
16
16
|
pinned: boolean;
|
|
17
17
|
/** The original provider's login id when the episode began; a sign-in moves it and releases a billing pin (senpi#4099). */
|
|
18
18
|
recoveryRevision?: string;
|
|
19
|
+
/** Wall-clock reset the usage limit behind a billing pin stated; the pin releases once it passes (senpi#4085). */
|
|
20
|
+
pinnedUntil?: number;
|
|
19
21
|
}
|
|
20
22
|
export type FallbackReason = "transient" | "refusal" | "hard-error" | "billing";
|
|
21
23
|
/** Absent: the original recovered after its cooldown. `fallback-unusable`: the fallback itself could not serve. */
|
|
@@ -4,7 +4,7 @@ import { firstUsableCandidate } from "./candidates.js";
|
|
|
4
4
|
import { baseSelector, canonicalizeFallbackChains, formatSelector, parseFallbackSelector, resolveChainKey, rungSelector, } from "./chains.js";
|
|
5
5
|
import { subscribeProviderAccountEvents } from "../extensions/builtin/anthropic-subscription/account-events.js";
|
|
6
6
|
import { CircuitProbes } from "./circuit-probes.js";
|
|
7
|
-
import { releaseOnCredentialChange } from "./credential-recovery.js";
|
|
7
|
+
import { releaseOnCredentialChange, releaseOnLimitReset } from "./credential-recovery.js";
|
|
8
8
|
import { pinAfterSwitch } from "./pin.js";
|
|
9
9
|
import { isPureNetworkTransient } from "./transient-network.js";
|
|
10
10
|
import { UnusableEntries } from "./unusable.js";
|
|
@@ -125,6 +125,7 @@ export class RetryFallbackController {
|
|
|
125
125
|
return false;
|
|
126
126
|
if (boundary === "turn")
|
|
127
127
|
await releaseOnCredentialChange(state, model, this.deps, this.unusable);
|
|
128
|
+
releaseOnLimitReset(state, model, this.deps, this.unusable);
|
|
128
129
|
if (state.pinned || !this.deps.isAuthAvailable(model.provider))
|
|
129
130
|
return false;
|
|
130
131
|
const stranded = this.unusable.isUnusable(current.model) && !this.unusable.isUnusable(model);
|
|
@@ -371,12 +372,18 @@ export class RetryFallbackController {
|
|
|
371
372
|
const recoveryRevision = prior
|
|
372
373
|
? prior.recoveryRevision
|
|
373
374
|
: await this.deps.recoveryRevision?.(current.model.provider);
|
|
375
|
+
const pin = pinAfterSwitch(prior, { from, fromProvider: current.model.provider, reason, limit });
|
|
376
|
+
// A new billing pin from a usage limit lasts until the reset the limit stated, when it named one (senpi#4085).
|
|
377
|
+
const pinnedUntil = pin.pinnedByBilling && prior?.pinnedByBilling !== true
|
|
378
|
+
? (this.deps.cooldowns.statedResetAt(from) ?? this.unusable.accountResetAt(current.model.provider))
|
|
379
|
+
: prior?.pinnedUntil;
|
|
374
380
|
this.state = {
|
|
375
381
|
...(recoveryRevision === undefined ? {} : { recoveryRevision }),
|
|
376
382
|
chainKey: candidate.chainKey,
|
|
377
383
|
originalThinkingLevel: prior?.originalThinkingLevel ?? current.thinkingLevel,
|
|
378
384
|
lastAppliedThinkingLevel: thinking,
|
|
379
|
-
...
|
|
385
|
+
...pin,
|
|
386
|
+
...(pinnedUntil === undefined ? {} : { pinnedUntil }),
|
|
380
387
|
};
|
|
381
388
|
const scope = limit === undefined ? {} : { limit };
|
|
382
389
|
this.deps.logger.info("fallback_applied", { from, to, chainKey: candidate.chainKey, reason, ...scope });
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class SelectorCooldowns {
|
|
8
8
|
private readonly expiresAtBySelector;
|
|
9
|
+
/**
|
|
10
|
+
* The same deadline on the wall clock. The monotonic clock stops while the machine sleeps, so a limit that
|
|
11
|
+
* reset during a sleep would stay held for its whole length again after waking (senpi#4085).
|
|
12
|
+
*/
|
|
13
|
+
private readonly wallExpiresAtBySelector;
|
|
9
14
|
/** Wall-clock end of a suppression whose length the provider stated (a reset or retry-after), not a default. */
|
|
10
15
|
private readonly statedResetAtBySelector;
|
|
11
16
|
private readonly now;
|
|
@@ -22,6 +27,8 @@ export declare class SelectorCooldowns {
|
|
|
22
27
|
clear(baseSelector: string): void;
|
|
23
28
|
/** Clears `baseSelector` and every rung of it (`baseSelector:<thinking level>`), never another model. */
|
|
24
29
|
clearModel(baseSelector: string): void;
|
|
30
|
+
/** The wall clock the stated resets are read on. */
|
|
31
|
+
wallClock(): number;
|
|
25
32
|
clearAll(): void;
|
|
26
33
|
private durationFor;
|
|
27
34
|
private statedDurationMs;
|
|
@@ -12,6 +12,11 @@ import { isTransportFailureText } from "./transient-network.js";
|
|
|
12
12
|
export class SelectorCooldowns {
|
|
13
13
|
constructor(now, random = Math.random, wallNow = now) {
|
|
14
14
|
this.expiresAtBySelector = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* The same deadline on the wall clock. The monotonic clock stops while the machine sleeps, so a limit that
|
|
17
|
+
* reset during a sleep would stay held for its whole length again after waking (senpi#4085).
|
|
18
|
+
*/
|
|
19
|
+
this.wallExpiresAtBySelector = new Map();
|
|
15
20
|
/** Wall-clock end of a suppression whose length the provider stated (a reset or retry-after), not a default. */
|
|
16
21
|
this.statedResetAtBySelector = new Map();
|
|
17
22
|
this.now = now;
|
|
@@ -21,6 +26,7 @@ export class SelectorCooldowns {
|
|
|
21
26
|
note(baseSelector, failure) {
|
|
22
27
|
const { ms, stated } = this.durationFor(failure);
|
|
23
28
|
this.expiresAtBySelector.set(baseSelector, this.now() + ms);
|
|
29
|
+
this.wallExpiresAtBySelector.set(baseSelector, this.wallNow() + ms);
|
|
24
30
|
if (stated)
|
|
25
31
|
this.statedResetAtBySelector.set(baseSelector, this.wallNow() + ms);
|
|
26
32
|
else
|
|
@@ -30,7 +36,7 @@ export class SelectorCooldowns {
|
|
|
30
36
|
const expiresAt = this.expiresAtBySelector.get(baseSelector);
|
|
31
37
|
if (expiresAt === undefined)
|
|
32
38
|
return false;
|
|
33
|
-
if (this.now() < expiresAt)
|
|
39
|
+
if (this.now() < expiresAt && this.wallNow() < (this.wallExpiresAtBySelector.get(baseSelector) ?? Infinity))
|
|
34
40
|
return true;
|
|
35
41
|
this.clear(baseSelector);
|
|
36
42
|
return false;
|
|
@@ -41,6 +47,7 @@ export class SelectorCooldowns {
|
|
|
41
47
|
}
|
|
42
48
|
clear(baseSelector) {
|
|
43
49
|
this.expiresAtBySelector.delete(baseSelector);
|
|
50
|
+
this.wallExpiresAtBySelector.delete(baseSelector);
|
|
44
51
|
this.statedResetAtBySelector.delete(baseSelector);
|
|
45
52
|
}
|
|
46
53
|
/** Clears `baseSelector` and every rung of it (`baseSelector:<thinking level>`), never another model. */
|
|
@@ -52,8 +59,13 @@ export class SelectorCooldowns {
|
|
|
52
59
|
this.clear(key);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
62
|
+
/** The wall clock the stated resets are read on. */
|
|
63
|
+
wallClock() {
|
|
64
|
+
return this.wallNow();
|
|
65
|
+
}
|
|
55
66
|
clearAll() {
|
|
56
67
|
this.expiresAtBySelector.clear();
|
|
68
|
+
this.wallExpiresAtBySelector.clear();
|
|
57
69
|
this.statedResetAtBySelector.clear();
|
|
58
70
|
}
|
|
59
71
|
durationFor(failure) {
|
|
@@ -14,5 +14,12 @@ type RecoveryDeps = Pick<RetryFallbackControllerDeps, "recoveryRevision" | "cool
|
|
|
14
14
|
* does not change the context that was refused (senpi#4099).
|
|
15
15
|
*/
|
|
16
16
|
export declare function releaseOnCredentialChange(state: ActiveFallbackState, original: Model<Api>, deps: RecoveryDeps, unusable: UnusableEntries): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* A billing pin a usage limit caused holds only until the reset that limit stated: the account serves again
|
|
19
|
+
* then, so the pin and the original's cooldown, unusable mark and circuit are released and the existing
|
|
20
|
+
* revert path tries it once, with no re-login (senpi#4085). A pin with no stated reset (an empty credit
|
|
21
|
+
* balance) still holds until a sign-in. A still-spent original falls back and pins again.
|
|
22
|
+
*/
|
|
23
|
+
export declare function releaseOnLimitReset(state: ActiveFallbackState, original: Model<Api>, deps: RecoveryDeps, unusable: UnusableEntries): void;
|
|
17
24
|
export {};
|
|
18
25
|
//# sourceMappingURL=credential-recovery.d.ts.map
|
|
@@ -29,4 +29,28 @@ export async function releaseOnCredentialChange(state, original, deps, unusable)
|
|
|
29
29
|
trigger: "sign-in",
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* A billing pin a usage limit caused holds only until the reset that limit stated: the account serves again
|
|
34
|
+
* then, so the pin and the original's cooldown, unusable mark and circuit are released and the existing
|
|
35
|
+
* revert path tries it once, with no re-login (senpi#4085). A pin with no stated reset (an empty credit
|
|
36
|
+
* balance) still holds until a sign-in. A still-spent original falls back and pins again.
|
|
37
|
+
*/
|
|
38
|
+
export function releaseOnLimitReset(state, original, deps, unusable) {
|
|
39
|
+
if (!state.pinnedByBilling || state.pinnedUntil === undefined)
|
|
40
|
+
return;
|
|
41
|
+
if (deps.cooldowns.wallClock() < state.pinnedUntil)
|
|
42
|
+
return;
|
|
43
|
+
state.pinnedByBilling = false;
|
|
44
|
+
state.pinnedUntil = undefined;
|
|
45
|
+
state.pinned = state.pinnedByRefusal;
|
|
46
|
+
const selector = formatSelector(original);
|
|
47
|
+
deps.cooldowns.clearModel(selector);
|
|
48
|
+
unusable.clear(original);
|
|
49
|
+
deps.circuits?.close(selector);
|
|
50
|
+
deps.logger.info("credential_recovery_released", {
|
|
51
|
+
chainKey: state.chainKey,
|
|
52
|
+
originalSelector: state.originalSelector,
|
|
53
|
+
trigger: "limit-reset",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
32
56
|
//# sourceMappingURL=credential-recovery.js.map
|