@code-yeongyu/senpi 2026.11.10-1 → 2026.11.10-3
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 +42 -0
- 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-YPAQXRP3.js → chunk-47T6YMI2.js} +2 -2
- package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
- package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
- package/dist/bundle/chunks/{chunk-3IYWH6RO.js → chunk-6Q7Y2G5A.js} +1 -1
- package/dist/bundle/chunks/{chunk-X4XWAB3J.js → chunk-AC6MDRAE.js} +81 -81
- package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
- package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
- package/dist/bundle/chunks/{chunk-LHEP3ACI.js → chunk-DEQLHN2J.js} +2 -2
- package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-DHAAWL2H.js} +1 -1
- package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-GPXZTSEX.js → chunk-DIHIEJFV.js} +3 -3
- 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-HI5JSLG5.js → chunk-H3VJVKUK.js} +4 -4
- package/dist/bundle/chunks/{chunk-L6NRDUWJ.js → chunk-HZDKQOWB.js} +1 -1
- package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
- package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-JSD6O3RJ.js} +2 -2
- package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-L3KVP7VJ.js} +3 -3
- package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
- 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-ARUV3US4.js → chunk-NN7NESW2.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-TGW4R7SI.js → chunk-O5WJVIEU.js} +1 -1
- package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
- 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-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-THYEZBUG.js → chunk-Z2C5N2LM.js} +2 -2
- package/dist/bundle/chunks/{cli-main-VG3C2EXI.js → cli-main-ZTT3N4DO.js} +1 -1
- package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-5KHVGSCY.js} +1 -1
- package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-3LQKUTYS.js → hand-entry-UV5XPMBF.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-CCASS7W4.js → host-command-BZN7WYYV.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-WWC7HXQ6.js → host-lifecycle-TNHASSFJ.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +1 -1
- package/dist/bundle/chunks/{host-session-command-YJB2FLKU.js → host-session-command-O5WYHZXH.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-CUN3WMUF.js → interactive-mode-OCU4NDPC.js} +1 -1
- package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
- package/dist/bundle/chunks/{main-P7XRC2OI.js → main-CNDP6HCF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-244X3P6L.js → multi-session-host-ODGJAGJP.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-UVWJOQA3.js → package-manager-cli-HSPUG4RJ.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-KOZPXU3U.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-5FG2OKSR.js → schedule-command-SG44FE23.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-5A24JYPE.js +3 -0
- package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-73YRS3GE.js} +1 -1
- package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +199 -196
- 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 +1 -1
- 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/core/agent-session-runtime.d.ts +2 -0
- package/dist/core/agent-session-runtime.js +5 -0
- package/dist/core/agent-session.d.ts +13 -3
- package/dist/core/agent-session.js +89 -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/egress/wire.d.ts +13 -0
- package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
- package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
- 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-provider.d.ts +4 -17
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +64 -75
- 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/token-store.d.ts +18 -0
- 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/prompt-preset/claude-fable-5-2.js +6 -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 +17 -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 +2 -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/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 +19 -13
- 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 -2
- package/dist/modes/rpc/admit-external-message-command.js +2 -10
- package/dist/modes/rpc/compact-admission-validation.js +4 -1
- package/dist/modes/rpc/connection-handler.d.ts +2 -0
- package/dist/modes/rpc/connection-handler.js +20 -8
- package/dist/modes/rpc/connection-roles.d.ts +2 -0
- package/dist/modes/rpc/connection-roles.js +4 -0
- package/dist/modes/rpc/custom-capability.d.ts +4 -0
- package/dist/modes/rpc/custom-capability.js +4 -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 +5 -1
- package/dist/modes/rpc/effect-commands.d.ts +21 -0
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +5 -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.d.ts +42 -1
- package/dist/modes/rpc/method-classes-schema.js +5 -2
- package/dist/modes/rpc/method-classes.d.ts +2 -0
- package/dist/modes/rpc/method-classes.js +3 -0
- package/dist/modes/rpc/multi-session-host.js +1 -1
- 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/session-attachment-gate.js +2 -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-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/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +22 -2
- package/docs/extensions.md +10 -0
- package/docs/rpc.md +50 -0
- package/docs/settings.md +1 -0
- 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-XH2MWTXA.js +0 -11
- package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
- package/dist/bundle/chunks/rpc-mode-G4I3HHPB.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-XWCFLJYC.js +0 -3
|
@@ -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 {
|
|
@@ -569,6 +569,8 @@ export class DefaultResourceLoader {
|
|
|
569
569
|
cwd: this.cwd,
|
|
570
570
|
agentDir: this.agentDir,
|
|
571
571
|
...this.settingsManager.getSettings().contextFiles,
|
|
572
|
+
// senpi#5567: user settings only; a project's settings never redirect the global file.
|
|
573
|
+
globalPath: this.settingsManager.getGlobalContextFilePath(),
|
|
572
574
|
}),
|
|
573
575
|
};
|
|
574
576
|
const resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;
|
|
@@ -6,5 +6,11 @@ export interface FallbackLogger {
|
|
|
6
6
|
export interface FallbackLoggerOptions {
|
|
7
7
|
maxBytes?: number;
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export interface SettledFallbackLogger extends FallbackLogger {
|
|
10
|
+
/** Resolves once the one-time removal of earlier log generations has finished and every line logged before it was written. */
|
|
11
|
+
settled(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** Resolves once every fallback logger created so far has finished its first-start removal and flushed its queue. */
|
|
14
|
+
export declare function settledFallbackLoggers(): Promise<void>;
|
|
15
|
+
export declare function createFallbackLogger(agentDir: string, options?: FallbackLoggerOptions): SettledFallbackLogger;
|
|
10
16
|
//# sourceMappingURL=log.d.ts.map
|
|
@@ -1,19 +1,81 @@
|
|
|
1
1
|
import { closeSync, fchmodSync, mkdirSync, openSync, writeSync } from "node:fs";
|
|
2
|
+
import { access, mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { rotateLogIfNeeded } from "../log-file-rotation.js";
|
|
4
|
-
import { redactSecrets } from "../sensitive-output.js";
|
|
5
|
+
import { redactSecrets, redactSensitiveTokenValues } from "../sensitive-output.js";
|
|
5
6
|
const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
6
7
|
const MAX_STRING_LENGTH = 200;
|
|
8
|
+
const MAX_QUEUED_LINES = 256;
|
|
7
9
|
const BLOCKED_KEY = /^(?:__proto__|constructor|prototype|headers?|env(?:ironment)?|authorization|credential(?:s)?|password|secret|token|api_?key|client_?secret)$/i;
|
|
8
10
|
const ALLOWED_DATA_KEY = /^(?:selector|candidate|currentSelector|originalSelector|from|to|model|provider|chainKey|reason|skipReason|classification|thinkingLevel|durationMs|retryAfterMs|error|errorMessage|warning|trigger)$/;
|
|
9
11
|
const SENSITIVE_TEXT = /((?:authorization\s*[:=]\s*(?:bearer|basic)\s+)|(?:bearer\s+)|(?:[?&](?:api[_-]?key|token|secret|password|auth(?:orization)?)=))[^\s&,"'}\]]+/gi;
|
|
12
|
+
const unsettledLoggers = new Set();
|
|
13
|
+
/** Resolves once every fallback logger created so far has finished its first-start removal and flushed its queue. */
|
|
14
|
+
export async function settledFallbackLoggers() {
|
|
15
|
+
await Promise.all([...unsettledLoggers]);
|
|
16
|
+
}
|
|
17
|
+
function errorCode(failure) {
|
|
18
|
+
if (typeof failure === "object" && failure !== null && "code" in failure && typeof failure.code === "string") {
|
|
19
|
+
return failure.code;
|
|
20
|
+
}
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
10
23
|
export function createFallbackLogger(agentDir, options = {}) {
|
|
11
24
|
const filePath = join(agentDir, "logs", "fallback.log");
|
|
12
25
|
const maxBytes = validMaxBytes(options.maxBytes);
|
|
13
26
|
let reportedWriteFailure = false;
|
|
27
|
+
// Lines logged while the earlier generations are still being removed wait here, so the removal can never delete them.
|
|
28
|
+
// They are redacted before they enter the queue and the queue is bounded: the newest lines win, the drops are counted.
|
|
29
|
+
let queued = [];
|
|
30
|
+
let dropped = 0;
|
|
31
|
+
function write(line) {
|
|
32
|
+
try {
|
|
33
|
+
writeLine(filePath, line, maxBytes);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!reportedWriteFailure) {
|
|
37
|
+
reportedWriteFailure = true;
|
|
38
|
+
console.error("Unable to write retry fallback debug log", error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const settled = retireUnsharedRegistryGenerations(filePath)
|
|
43
|
+
.then(() => undefined)
|
|
44
|
+
.catch((error) => {
|
|
45
|
+
reportedWriteFailure = true;
|
|
46
|
+
console.error("Unable to remove earlier retry fallback debug log generations", error);
|
|
47
|
+
return error;
|
|
48
|
+
})
|
|
49
|
+
.then((failure) => {
|
|
50
|
+
const lines = queued ?? [];
|
|
51
|
+
queued = undefined;
|
|
52
|
+
for (const line of lines)
|
|
53
|
+
write(line);
|
|
54
|
+
if (dropped > 0) {
|
|
55
|
+
write(formatLine("warn", "queued_lines_dropped", {
|
|
56
|
+
reason: `${dropped} lines dropped while earlier log generations were removed`,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
if (failure !== undefined) {
|
|
60
|
+
// The code only: the error text names the agent dir's absolute path.
|
|
61
|
+
write(formatLine("warn", "retire_generations_failed", {
|
|
62
|
+
errorMessage: `removal failed: ${errorCode(failure)}`,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
unsettledLoggers.add(settled);
|
|
67
|
+
void settled.then(() => unsettledLoggers.delete(settled));
|
|
14
68
|
function log(level, event, data) {
|
|
15
69
|
try {
|
|
16
70
|
const line = formatLine(level, event, data);
|
|
71
|
+
if (queued !== undefined) {
|
|
72
|
+
queued.push(redactSensitiveTokenValues(line, "[redacted]"));
|
|
73
|
+
if (queued.length > MAX_QUEUED_LINES) {
|
|
74
|
+
queued.shift();
|
|
75
|
+
dropped += 1;
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
17
79
|
writeLine(filePath, line, maxBytes);
|
|
18
80
|
}
|
|
19
81
|
catch (error) {
|
|
@@ -27,8 +89,23 @@ export function createFallbackLogger(agentDir, options = {}) {
|
|
|
27
89
|
debug: (event, data) => log("debug", event, data),
|
|
28
90
|
info: (event, data) => log("info", event, data),
|
|
29
91
|
warn: (event, data) => log("warn", event, data),
|
|
92
|
+
settled: () => settled,
|
|
30
93
|
};
|
|
31
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Generations written before every engine copy in a process shared one resolved-value registry (omo#12965) are
|
|
97
|
+
* removed once per agent dir, the first time an engine with the shared registry starts on it; the marker makes every
|
|
98
|
+
* later start leave the logs alone.
|
|
99
|
+
*/
|
|
100
|
+
async function retireUnsharedRegistryGenerations(filePath) {
|
|
101
|
+
const marker = join(dirname(filePath), ".fallback-log-v2");
|
|
102
|
+
if (await access(marker).then(() => true, () => false))
|
|
103
|
+
return;
|
|
104
|
+
await rm(`${filePath}.1`, { force: true });
|
|
105
|
+
await rm(filePath, { force: true });
|
|
106
|
+
await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
107
|
+
await writeFile(marker, "", { mode: 0o600 });
|
|
108
|
+
}
|
|
32
109
|
function validMaxBytes(value) {
|
|
33
110
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : DEFAULT_MAX_BYTES;
|
|
34
111
|
}
|
|
@@ -51,7 +128,7 @@ function formatLine(level, event, data) {
|
|
|
51
128
|
}
|
|
52
129
|
function writeLine(filePath, line, maxBytes) {
|
|
53
130
|
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
54
|
-
const text = `${line}\n`;
|
|
131
|
+
const text = `${redactSensitiveTokenValues(line, "[redacted]")}\n`;
|
|
55
132
|
rotateLogIfNeeded(filePath, Buffer.byteLength(text), maxBytes);
|
|
56
133
|
const descriptor = openSync(filePath, "a", 0o600);
|
|
57
134
|
try {
|
|
@@ -66,13 +143,13 @@ function serializeValue(value, seen) {
|
|
|
66
143
|
if (typeof value === "string")
|
|
67
144
|
return safeText(value);
|
|
68
145
|
if (typeof value === "bigint")
|
|
69
|
-
return value.toString();
|
|
146
|
+
return safeText(value.toString());
|
|
70
147
|
if (typeof value === "number" || typeof value === "boolean" || value === null)
|
|
71
148
|
return value;
|
|
72
149
|
if (typeof value === "undefined")
|
|
73
150
|
return undefined;
|
|
74
151
|
if (typeof value === "function" || typeof value === "symbol")
|
|
75
|
-
return String(value);
|
|
152
|
+
return safeText(String(value));
|
|
76
153
|
if (seen.has(value))
|
|
77
154
|
return "[Circular]";
|
|
78
155
|
seen.add(value);
|
|
@@ -83,7 +160,7 @@ function serializeValue(value, seen) {
|
|
|
83
160
|
if (!BLOCKED_KEY.test(key)) {
|
|
84
161
|
const safeValue = serializeValue(item, seen);
|
|
85
162
|
if (safeValue !== undefined)
|
|
86
|
-
result[key] = safeValue;
|
|
163
|
+
result[safeText(key)] = safeValue;
|
|
87
164
|
}
|
|
88
165
|
}
|
|
89
166
|
return result;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { redactSecrets } from "../sensitive-output.js";
|
|
1
2
|
const EXCERPT_LIMIT = 500;
|
|
2
3
|
/**
|
|
3
4
|
* A model configuration error is never retried or fallen back: another model would answer a request the caller did
|
|
@@ -16,7 +17,7 @@ export function createModelConfigErrorReporter(logger, emit) {
|
|
|
16
17
|
if (reported.has(key))
|
|
17
18
|
return;
|
|
18
19
|
reported.add(key);
|
|
19
|
-
const errorMessage = message.errorMessage ?? "";
|
|
20
|
+
const errorMessage = redactSecrets(message.errorMessage ?? "");
|
|
20
21
|
logger.warn("model_config_error", { model, provider: message.provider, errorMessage });
|
|
21
22
|
emit({
|
|
22
23
|
type: "model_config_error",
|
|
@@ -440,6 +440,8 @@ export declare class SessionManager {
|
|
|
440
440
|
private _transcriptEntryBytes;
|
|
441
441
|
private _serializedFileEntries;
|
|
442
442
|
isPersisted(): boolean;
|
|
443
|
+
/** Give an unclaimed in-memory session its first file without rebuilding or changing its identity. */
|
|
444
|
+
enablePersistence(): Promise<void>;
|
|
443
445
|
getCwd(): string;
|
|
444
446
|
getSessionDir(): string;
|
|
445
447
|
usesDefaultSessionDir(): boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getCurrentSystemMessage, normalizeProviderId } from "@earendil-works/pi-ai";
|
|
2
2
|
import { randomBytes, randomUUID } from "crypto";
|
|
3
3
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readSync, rmSync, statSync, writeFileSync, } from "fs";
|
|
4
|
-
import { appendFile, open, readdir, rm } from "fs/promises";
|
|
4
|
+
import { appendFile, mkdir, open, readdir, rm } from "fs/promises";
|
|
5
5
|
import { basename, join, resolve } from "path";
|
|
6
6
|
import { StringDecoder } from "string_decoder";
|
|
7
7
|
import { APP_NAME, getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
@@ -1230,6 +1230,20 @@ export class SessionManager {
|
|
|
1230
1230
|
isPersisted() {
|
|
1231
1231
|
return this.persist;
|
|
1232
1232
|
}
|
|
1233
|
+
/** Give an unclaimed in-memory session its first file without rebuilding or changing its identity. */
|
|
1234
|
+
async enablePersistence() {
|
|
1235
|
+
if (this.persist)
|
|
1236
|
+
return;
|
|
1237
|
+
const dir = getDefaultSessionDirPath(this.cwd);
|
|
1238
|
+
await mkdir(dir, { recursive: true });
|
|
1239
|
+
const header = this.fileEntries[0];
|
|
1240
|
+
const path = join(dir, `${header.timestamp.replace(/[:.]/g, "-")}_${this.sessionId}.jsonl`);
|
|
1241
|
+
reserveSessionWrite(path);
|
|
1242
|
+
this.sessionDir = dir;
|
|
1243
|
+
this.sessionFile = path;
|
|
1244
|
+
this.persist = true;
|
|
1245
|
+
registerSessionWriter(this);
|
|
1246
|
+
}
|
|
1233
1247
|
getCwd() {
|
|
1234
1248
|
return this.cwd;
|
|
1235
1249
|
}
|
|
@@ -302,6 +302,12 @@ export declare class SettingsManager {
|
|
|
302
302
|
getSettings(): Settings;
|
|
303
303
|
getGlobalSettings(): Settings;
|
|
304
304
|
getProjectSettings(): Settings;
|
|
305
|
+
/**
|
|
306
|
+
* `contextFiles.globalPath` from the user's own layers only: session overrides, then global
|
|
307
|
+
* settings. A project's `.senpi/settings.json` never redirects the user's global context file
|
|
308
|
+
* (senpi#5567), so the project layer is deliberately skipped.
|
|
309
|
+
*/
|
|
310
|
+
getGlobalContextFilePath(): string | undefined;
|
|
305
311
|
/** The project settings file at `path` as stored: parsed and migrated like a load, but never gated by trust. */
|
|
306
312
|
readProjectSettingsFile(path: string): ProjectSettingsFile;
|
|
307
313
|
/**
|
|
@@ -697,6 +697,14 @@ export class SettingsManager {
|
|
|
697
697
|
getProjectSettings() {
|
|
698
698
|
return structuredClone(this.projectSettings);
|
|
699
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* `contextFiles.globalPath` from the user's own layers only: session overrides, then global
|
|
702
|
+
* settings. A project's `.senpi/settings.json` never redirects the user's global context file
|
|
703
|
+
* (senpi#5567), so the project layer is deliberately skipped.
|
|
704
|
+
*/
|
|
705
|
+
getGlobalContextFilePath() {
|
|
706
|
+
return this.runtimeOverrides.contextFiles?.globalPath ?? this.globalSettings.contextFiles?.globalPath;
|
|
707
|
+
}
|
|
700
708
|
/** The project settings file at `path` as stored: parsed and migrated like a load, but never gated by trust. */
|
|
701
709
|
readProjectSettingsFile(path) {
|
|
702
710
|
let content;
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import type { SessionManager } from "./session-manager.ts";
|
|
2
1
|
/**
|
|
3
|
-
* Who started a turn, for the
|
|
4
|
-
*
|
|
5
|
-
* - `
|
|
6
|
-
* - `
|
|
7
|
-
*
|
|
2
|
+
* Who started a turn, for the remote approval-mode rule (senpi#6342: a turn follows the permission setting of the machine
|
|
3
|
+
* it runs on, and no origin outlives the turn it started):
|
|
4
|
+
* - `local`: this machine's own input, or input from a device the forwarder stated is the same account;
|
|
5
|
+
* - `delegated`: input from a device the forwarder did not state is the same account, or the one run such a device's
|
|
6
|
+
* record started;
|
|
8
7
|
* - `engine`: a turn no client record started (an extension's message, a goal or loop turn, restored input).
|
|
9
8
|
* A steer joins the running turn (`join`) and never changes its origin.
|
|
10
9
|
*/
|
|
11
|
-
export type TurnOrigin = "local" | "delegated" | "
|
|
10
|
+
export type TurnOrigin = "local" | "delegated" | "engine";
|
|
12
11
|
export type InputTurnMark = TurnOrigin | "join";
|
|
13
12
|
export type ConnectionOrigin = "local" | "delegated";
|
|
14
|
-
type OriginSessionManager = Pick<SessionManager, "getEntries" | "appendCustomEntry">;
|
|
15
13
|
/** The mark an input gets where it is created: explicit for client records, `local` for the TUI, else `engine`. */
|
|
16
14
|
export declare function inputTurnMark(input: {
|
|
17
15
|
readonly mode?: "steer" | "followUp";
|
|
@@ -23,11 +21,19 @@ export declare function markInputTurnOrigin(message: object, mark: InputTurnMark
|
|
|
23
21
|
export declare function copyInputTurnMark(from: object, to: object): void;
|
|
24
22
|
/** Whether a message carries an origin mark: a user message always opens a turn's origin, any other only when marked. */
|
|
25
23
|
export declare function hasInputTurnMark(message: object): boolean;
|
|
26
|
-
/**
|
|
24
|
+
/** Runs `handle`, and every async task it starts, as the record `origin` describes. */
|
|
25
|
+
export declare function runInRecordScope<T>(origin: ConnectionOrigin, handle: () => Promise<T>): Promise<T>;
|
|
26
|
+
/** Whether the work being created belongs to an open cross-account record's own handling. */
|
|
27
|
+
export declare function recordIsDelegated(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* A run starts as engine work, or as delegated work when a cross-account record's own handling starts it; a retry keeps
|
|
30
|
+
* the turn it continues. The user message that opens a turn sets its origin, a steer keeps it.
|
|
31
|
+
*/
|
|
27
32
|
export declare function noteRunStart(sessionManager: object): void;
|
|
33
|
+
/** A retry or recovery run repeats the turn it continues, so it keeps that turn's origin until the continuation ends. */
|
|
34
|
+
export declare function noteTurnContinuation(sessionManager: object): void;
|
|
35
|
+
export declare function endTurnContinuation(sessionManager: object): void;
|
|
28
36
|
export declare function noteUserMessageStart(sessionManager: object, message: object): void;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export declare function decidingTurnIsDelegated(sessionManager: Pick<SessionManager, "getEntries">): boolean;
|
|
32
|
-
export {};
|
|
37
|
+
/** Whether the deciding turn counts as delegated-origin: a cross-account device started this turn or this run. */
|
|
38
|
+
export declare function decidingTurnIsDelegated(sessionManager: object): boolean;
|
|
33
39
|
//# sourceMappingURL=turn-origin.d.ts.map
|
package/dist/core/turn-origin.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
const inputMarks = new WeakMap();
|
|
3
3
|
const currentOrigins = new WeakMap();
|
|
4
|
-
const
|
|
4
|
+
const recordScopes = new AsyncLocalStorage();
|
|
5
|
+
const continuingTurns = new WeakSet();
|
|
5
6
|
/** The mark an input gets where it is created: explicit for client records, `local` for the TUI, else `engine`. */
|
|
6
7
|
export function inputTurnMark(input) {
|
|
7
8
|
if (input.mode === "steer")
|
|
8
9
|
return "join";
|
|
9
10
|
if (input.connectionOrigin !== undefined)
|
|
10
11
|
return input.connectionOrigin;
|
|
12
|
+
if (recordIsDelegated())
|
|
13
|
+
return "delegated";
|
|
11
14
|
return input.source === undefined || input.source === "interactive" ? "local" : "engine";
|
|
12
15
|
}
|
|
13
16
|
export function markInputTurnOrigin(message, mark) {
|
|
@@ -23,9 +26,34 @@ export function copyInputTurnMark(from, to) {
|
|
|
23
26
|
export function hasInputTurnMark(message) {
|
|
24
27
|
return inputMarks.has(message);
|
|
25
28
|
}
|
|
26
|
-
/**
|
|
29
|
+
/** Runs `handle`, and every async task it starts, as the record `origin` describes. */
|
|
30
|
+
export function runInRecordScope(origin, handle) {
|
|
31
|
+
return recordScopes.run({ origin, open: true }, handle);
|
|
32
|
+
}
|
|
33
|
+
/** Whether the work being created belongs to an open cross-account record's own handling. */
|
|
34
|
+
export function recordIsDelegated() {
|
|
35
|
+
const scope = recordScopes.getStore();
|
|
36
|
+
return scope?.open === true && scope.origin === "delegated";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A run starts as engine work, or as delegated work when a cross-account record's own handling starts it; a retry keeps
|
|
40
|
+
* the turn it continues. The user message that opens a turn sets its origin, a steer keeps it.
|
|
41
|
+
*/
|
|
27
42
|
export function noteRunStart(sessionManager) {
|
|
28
|
-
|
|
43
|
+
if (continuingTurns.has(sessionManager))
|
|
44
|
+
return;
|
|
45
|
+
const delegated = recordIsDelegated();
|
|
46
|
+
const scope = recordScopes.getStore();
|
|
47
|
+
if (scope !== undefined)
|
|
48
|
+
scope.open = false;
|
|
49
|
+
currentOrigins.set(sessionManager, delegated ? "delegated" : "engine");
|
|
50
|
+
}
|
|
51
|
+
/** A retry or recovery run repeats the turn it continues, so it keeps that turn's origin until the continuation ends. */
|
|
52
|
+
export function noteTurnContinuation(sessionManager) {
|
|
53
|
+
continuingTurns.add(sessionManager);
|
|
54
|
+
}
|
|
55
|
+
export function endTurnContinuation(sessionManager) {
|
|
56
|
+
continuingTurns.delete(sessionManager);
|
|
29
57
|
}
|
|
30
58
|
export function noteUserMessageStart(sessionManager, message) {
|
|
31
59
|
const mark = inputMarks.get(message);
|
|
@@ -33,32 +61,8 @@ export function noteUserMessageStart(sessionManager, message) {
|
|
|
33
61
|
return;
|
|
34
62
|
currentOrigins.set(sessionManager, mark ?? "engine");
|
|
35
63
|
}
|
|
36
|
-
|
|
37
|
-
if (delegatedOriginOf(sessionManager))
|
|
38
|
-
return;
|
|
39
|
-
delegatedSessions.add(sessionManager);
|
|
40
|
-
sessionManager.appendCustomEntry(DELEGATED_ORIGIN_ENTRY, { delegatedOrigin: true }, { startupBookkeeping: true });
|
|
41
|
-
}
|
|
42
|
-
// How many entries a session's scan has already found free of the marker: each gated decision reads only new entries.
|
|
43
|
-
const scannedEntries = new WeakMap();
|
|
44
|
-
function delegatedOriginOf(sessionManager) {
|
|
45
|
-
if (delegatedSessions.has(sessionManager))
|
|
46
|
-
return true;
|
|
47
|
-
const entries = sessionManager.getEntries();
|
|
48
|
-
const persisted = entries
|
|
49
|
-
.slice(scannedEntries.get(sessionManager) ?? 0)
|
|
50
|
-
.some((entry) => entry.type === "custom" && entry.customType === DELEGATED_ORIGIN_ENTRY);
|
|
51
|
-
if (persisted)
|
|
52
|
-
delegatedSessions.add(sessionManager);
|
|
53
|
-
else
|
|
54
|
-
scannedEntries.set(sessionManager, entries.length);
|
|
55
|
-
return persisted;
|
|
56
|
-
}
|
|
57
|
-
/** Whether the deciding turn counts as delegated-origin: a remote device started it, or nobody did on a remote-driven session. */
|
|
64
|
+
/** Whether the deciding turn counts as delegated-origin: a cross-account device started this turn or this run. */
|
|
58
65
|
export function decidingTurnIsDelegated(sessionManager) {
|
|
59
|
-
|
|
60
|
-
if (origin === "delegated")
|
|
61
|
-
return true;
|
|
62
|
-
return origin === "engine" && delegatedOriginOf(sessionManager);
|
|
66
|
+
return currentOrigins.get(sessionManager) === "delegated";
|
|
63
67
|
}
|
|
64
68
|
//# sourceMappingURL=turn-origin.js.map
|
|
@@ -4,5 +4,6 @@ export declare function serviceTierForProvider(provider: string | undefined, ser
|
|
|
4
4
|
export declare function ultrafastSelectionWarning(model: {
|
|
5
5
|
readonly provider: string;
|
|
6
6
|
readonly id: string;
|
|
7
|
+
readonly upstreamModelId?: string;
|
|
7
8
|
}, serviceTier: ServiceTier | undefined): string | undefined;
|
|
8
9
|
//# sourceMappingURL=ultrafast-lanes.d.ts.map
|