@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
|
@@ -96,7 +96,7 @@ import { deliveryIdOf, ExternalAdmission } from "./external-admission.js";
|
|
|
96
96
|
import { HELD_INPUT_ENTRY, HeldInputs, heldInputHistory } from "./held-inputs.js";
|
|
97
97
|
import { shouldWarnHighReasoning } from "./high-reasoning-warning.js";
|
|
98
98
|
import { customTurnOrigin, inputAttributionOf, inputOriginOf } from "./input-origin.js";
|
|
99
|
-
import { copyInputTurnMark, hasInputTurnMark, inputTurnMark, markInputTurnOrigin, noteRunStart, noteUserMessageStart, } from "./turn-origin.js";
|
|
99
|
+
import { copyInputTurnMark, endTurnContinuation, hasInputTurnMark, inputTurnMark, markInputTurnOrigin, noteRunStart, noteTurnContinuation, recordIsDelegated, noteUserMessageStart, } from "./turn-origin.js";
|
|
100
100
|
import { releaseDecisionPreset } from "./permission-decision.js";
|
|
101
101
|
import { markInterrupted } from "./interrupted-tool-result.js";
|
|
102
102
|
import { deliveredReceipts, itemNoticeMessage, QueuedDeliveryCleared, } from "./itemized-send.js";
|
|
@@ -134,6 +134,7 @@ import { followProviderAccountChanges, RetryFallbackController, } from "./retry-
|
|
|
134
134
|
import { SelectorCooldowns } from "./retry-fallback/cooldown.js";
|
|
135
135
|
import { classifyRateLimitedWait, degradeWithoutFallback, nextInTurnDelayMs, probeBackSchedule, } from "./retry-fallback/hint-policy.js";
|
|
136
136
|
import { createFallbackLogger } from "./retry-fallback/log.js";
|
|
137
|
+
import { redactAssistantErrorText } from "./assistant-error-redaction.js";
|
|
137
138
|
import { createModelConfigErrorReporter, isModelConfigFailure, } from "./retry-fallback/model-config-error.js";
|
|
138
139
|
import { ProbeBackScheduler } from "./retry-fallback/probe-scheduler.js";
|
|
139
140
|
import { REQUEST_SHAPE_FAILURE_TEXT, REQUEST_SHAPE_RECOVERY_ENTRY_TYPE, REQUEST_SHAPE_RECOVERY_NOTE, RequestShapeRecovery, } from "./retry-fallback/request-shape.js";
|
|
@@ -619,8 +620,9 @@ export class AgentSession {
|
|
|
619
620
|
this._abortDuringBeforeSettle = false;
|
|
620
621
|
this._baseToolDefinitions = new Map();
|
|
621
622
|
this._toolChildHostTargets = new Map();
|
|
622
|
-
/** Settles once dispose() has stopped the tool child, swept what it launched,
|
|
623
|
+
/** Settles once dispose() has stopped the tool child, swept what it launched, closed its in-process offload jobs, and the fallback log's first-start removal has finished. */
|
|
623
624
|
this._toolChildClosed = Promise.resolve();
|
|
625
|
+
this._fallbackLogSettled = Promise.resolve();
|
|
624
626
|
this._usesDefaultTools = false;
|
|
625
627
|
/** Policy tools withheld from the model, retained so disarming can restore direct access. */
|
|
626
628
|
this._withheldEvalOnlyToolNames = new Set();
|
|
@@ -669,6 +671,7 @@ export class AgentSession {
|
|
|
669
671
|
this._endSelectedModels = new WeakMap();
|
|
670
672
|
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
671
673
|
this._handleAgentEvent = (event, signal) => {
|
|
674
|
+
redactAssistantErrorText(event);
|
|
672
675
|
let queuedEvent = event;
|
|
673
676
|
let pendingMessage;
|
|
674
677
|
// Lifecycle events this event raises are delivered just before it, in the same queue (LifecycleEventSequencer).
|
|
@@ -844,6 +847,7 @@ export class AgentSession {
|
|
|
844
847
|
this._modelRegistry = config.modelRegistry ?? new ModelRegistry(modelRuntime);
|
|
845
848
|
this._agentDir = config.agentDir ?? getAgentDir();
|
|
846
849
|
const fallbackLogger = createFallbackLogger(this._agentDir);
|
|
850
|
+
this._fallbackLogSettled = fallbackLogger.settled();
|
|
847
851
|
this._modelConfigErrors = createModelConfigErrorReporter(fallbackLogger, (event) => this._emit(event));
|
|
848
852
|
this._sessionLogger = createSessionLogger(this._agentDir, {
|
|
849
853
|
context: () => ({
|
|
@@ -2064,7 +2068,7 @@ export class AgentSession {
|
|
|
2064
2068
|
get agentDir() {
|
|
2065
2069
|
return this._agentDir;
|
|
2066
2070
|
}
|
|
2067
|
-
/** Resolves after dispose() once the tool child has exited
|
|
2071
|
+
/** Resolves after dispose() once the tool child has exited, its launched groups are swept and the fallback log has stopped writing; awaited by disposers. */
|
|
2068
2072
|
whenToolChildClosed() {
|
|
2069
2073
|
return this._toolChildClosed;
|
|
2070
2074
|
}
|
|
@@ -3519,7 +3523,7 @@ export class AgentSession {
|
|
|
3519
3523
|
?.close()
|
|
3520
3524
|
.catch((error) => this._sessionLogger.warn("tool_child_close_failed", { error: String(error) })) ??
|
|
3521
3525
|
Promise.resolve();
|
|
3522
|
-
this._toolChildClosed = Promise.all([inProcessClosed, childClosed]).then(() => undefined);
|
|
3526
|
+
this._toolChildClosed = Promise.all([inProcessClosed, childClosed, this._fallbackLogSettled]).then(() => undefined);
|
|
3523
3527
|
this._disconnectFromAgent();
|
|
3524
3528
|
this._unsubscribeSettingsSource?.();
|
|
3525
3529
|
this._unsubscribeSettingsSource = undefined;
|
|
@@ -5510,6 +5514,8 @@ export class AgentSession {
|
|
|
5510
5514
|
// Restored input with no source keeps no mark: an unmarked turn is engine work, never a trusted origin.
|
|
5511
5515
|
if (input.source !== undefined)
|
|
5512
5516
|
markInputTurnOrigin(message, inputTurnMark(input));
|
|
5517
|
+
else if (recordIsDelegated())
|
|
5518
|
+
markInputTurnOrigin(message, "delegated");
|
|
5513
5519
|
if (input.inputId !== undefined)
|
|
5514
5520
|
this._queuedInputDispositions.track(message, input.inputId);
|
|
5515
5521
|
this._queuedMessages.set(queuedId, message);
|
|
@@ -5900,6 +5906,10 @@ export class AgentSession {
|
|
|
5900
5906
|
};
|
|
5901
5907
|
// A delivery that found a run already streaming becomes a steer or follow-up: it keeps the origin it was admitted with.
|
|
5902
5908
|
copyInputTurnMark(message, appMessage);
|
|
5909
|
+
// senpi#6342: a custom message a cross-account record's own handling sends carries that origin wherever it waits
|
|
5910
|
+
// (joining a streaming run, parked behind a compaction, held for the next turn), so the turn it opens still asks.
|
|
5911
|
+
if (!hasInputTurnMark(appMessage) && recordIsDelegated())
|
|
5912
|
+
markInputTurnOrigin(appMessage, "delegated");
|
|
5903
5913
|
if (this._sizesOnlyStartup && options?.triggerTurn === true) {
|
|
5904
5914
|
// Size inspection makes no model call (#3483): keep the message for a turn that never starts.
|
|
5905
5915
|
this._pendingNextTurnMessages.push(appMessage);
|
|
@@ -6574,7 +6584,7 @@ export class AgentSession {
|
|
|
6574
6584
|
}
|
|
6575
6585
|
}
|
|
6576
6586
|
/**
|
|
6577
|
-
* A refused engine turn still records its message and settles a cross-session delivery it carries. A
|
|
6587
|
+
* A refused engine turn still records its message and settles a cross-session delivery it carries. A message
|
|
6578
6588
|
* whose entry the file refuses leaves the context too: its sender redelivers it, and it must not appear twice.
|
|
6579
6589
|
*/
|
|
6580
6590
|
_recordRefusedEngineTurnMessage(appMessage) {
|
|
@@ -6583,12 +6593,17 @@ export class AgentSession {
|
|
|
6583
6593
|
this.externalAdmission.observePersisted(appMessage);
|
|
6584
6594
|
}
|
|
6585
6595
|
catch (error) {
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6596
|
+
// The session file refused the entry: the message leaves the context too, so the context matches the
|
|
6597
|
+
// file, and the failure follows the same contract as the main persistence path — it is recorded,
|
|
6598
|
+
// logged, and emitted (senpi#2972). This holds for non-delivery messages as well as deliveries.
|
|
6599
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6600
|
+
const index = this.agent.state.messages.lastIndexOf(appMessage);
|
|
6601
|
+
if (index >= 0)
|
|
6602
|
+
this.agent.state.messages.splice(index, 1);
|
|
6603
|
+
this._transcriptWriteFailures.record(appMessage, error);
|
|
6604
|
+
this._sessionLogger.warn("transcript_write_failed", { role: appMessage.role, error: errorMessage });
|
|
6605
|
+
this._emit({ type: "transcript_write_failed", role: appMessage.role, errorMessage });
|
|
6606
|
+
this.externalAdmission.observeRefused(appMessage, errorMessage);
|
|
6592
6607
|
this._takePersistWatcher(appMessage)?.failed(error);
|
|
6593
6608
|
}
|
|
6594
6609
|
}
|
|
@@ -6674,12 +6689,27 @@ export class AgentSession {
|
|
|
6674
6689
|
const start = { type: "message_start", message: appMessage };
|
|
6675
6690
|
this._recoveryIdentity?.observe(start);
|
|
6676
6691
|
const entryId = this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details, { recoveryOwner: recoveryCarrier(start) });
|
|
6677
|
-
|
|
6692
|
+
// The entry is written: only a failure of the write itself counts as refused. Bookkeeping (ticket,
|
|
6693
|
+
// watcher settlement, revision) runs before any notification, and each notification is guarded on its
|
|
6694
|
+
// own, so a throwing listener can neither leave a persisted delivery unsettled nor suppress a later
|
|
6695
|
+
// notification (senpi#2972).
|
|
6678
6696
|
this._recordWrittenTicket(appMessage, entryId);
|
|
6679
6697
|
this._takePersistWatcher(appMessage)?.persisted(entryId);
|
|
6680
6698
|
this._incrementMessageRevision();
|
|
6681
|
-
|
|
6682
|
-
|
|
6699
|
+
const notify = (emit) => {
|
|
6700
|
+
try {
|
|
6701
|
+
emit();
|
|
6702
|
+
}
|
|
6703
|
+
catch (error) {
|
|
6704
|
+
this._sessionLogger.warn("custom_message_listener_failed", {
|
|
6705
|
+
customType: appMessage.customType,
|
|
6706
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6707
|
+
});
|
|
6708
|
+
}
|
|
6709
|
+
};
|
|
6710
|
+
notify(() => this._emitEntryAppended(entryId));
|
|
6711
|
+
notify(() => this._emit(start));
|
|
6712
|
+
notify(() => this._emit({ type: "message_end", message: appMessage }));
|
|
6683
6713
|
}
|
|
6684
6714
|
_flushPendingCustomMessages() {
|
|
6685
6715
|
const pending = this._pendingCustomMessages;
|
|
@@ -9357,7 +9387,7 @@ export class AgentSession {
|
|
|
9357
9387
|
}
|
|
9358
9388
|
this._scheduledContinuationRecompacted = true;
|
|
9359
9389
|
}
|
|
9360
|
-
async _continueAgentAfterCurrentRun(options = {}, retryTimeoutMs) {
|
|
9390
|
+
async _continueAgentAfterCurrentRun(options = {}, retryTimeoutMs, keepsTurnOrigin = false) {
|
|
9361
9391
|
await this.agent.waitForIdle();
|
|
9362
9392
|
try {
|
|
9363
9393
|
if (this.agent.state.isStreaming)
|
|
@@ -9384,6 +9414,8 @@ export class AgentSession {
|
|
|
9384
9414
|
this._transcriptWriteFailures.startRun();
|
|
9385
9415
|
await runBoundedRetryContinuation({
|
|
9386
9416
|
continueRun: async () => {
|
|
9417
|
+
if (keepsTurnOrigin)
|
|
9418
|
+
noteTurnContinuation(this.sessionManager);
|
|
9387
9419
|
if (this._continueWithQueuedInput) {
|
|
9388
9420
|
// Joined input of a start that never ran: the transcript tail may be anything, even empty.
|
|
9389
9421
|
this._continueWithQueuedInput = false;
|
|
@@ -9428,6 +9460,7 @@ export class AgentSession {
|
|
|
9428
9460
|
throw error;
|
|
9429
9461
|
}
|
|
9430
9462
|
finally {
|
|
9463
|
+
endTurnContinuation(this.sessionManager);
|
|
9431
9464
|
this._scheduledContinuationRecompacted = false;
|
|
9432
9465
|
this._continueWithQueuedInput = false;
|
|
9433
9466
|
}
|
|
@@ -9459,13 +9492,13 @@ export class AgentSession {
|
|
|
9459
9492
|
this._continueWithQueuedInput = true;
|
|
9460
9493
|
this._scheduleContinuationAfterCurrentEvent();
|
|
9461
9494
|
}
|
|
9462
|
-
_scheduleContinuationAfterCurrentEvent(options = {}, retryContinuation = false, retryTimeoutMs) {
|
|
9495
|
+
_scheduleContinuationAfterCurrentEvent(options = {}, retryContinuation = false, retryTimeoutMs, keepsTurnOrigin = retryContinuation) {
|
|
9463
9496
|
// Tool hooks wait for queued message persistence, so continue() cannot run inside this event promise.
|
|
9464
9497
|
const currentEventQueue = this._agentEventQueue;
|
|
9465
9498
|
const finishContinuationWork = this._sessionWorkBarrier.begin();
|
|
9466
9499
|
const continueAfterEvent = async () => {
|
|
9467
9500
|
try {
|
|
9468
|
-
const outcome = await this._continueAgentAfterCurrentRun(options, retryTimeoutMs);
|
|
9501
|
+
const outcome = await this._continueAgentAfterCurrentRun(options, retryTimeoutMs, keepsTurnOrigin);
|
|
9469
9502
|
if (retryContinuation && outcome === "taken-over") {
|
|
9470
9503
|
DEFERRED_RETRY_QUEUE_OWNERS.delete(this);
|
|
9471
9504
|
}
|
|
@@ -9607,7 +9640,8 @@ export class AgentSession {
|
|
|
9607
9640
|
// kept tail still covers (one per rung climbed so far); the retry must
|
|
9608
9641
|
// continue from the turn they failed to answer, so strip the whole run.
|
|
9609
9642
|
this._stripTrailingFailedAssistants();
|
|
9610
|
-
this.
|
|
9643
|
+
// The recovery run repeats this turn, so it keeps the turn's origin.
|
|
9644
|
+
this._scheduleContinuationAfterCurrentEvent({}, false, undefined, true);
|
|
9611
9645
|
return true;
|
|
9612
9646
|
}
|
|
9613
9647
|
else if (this.pendingMessageCount > 0) {
|
|
@@ -11947,8 +11981,45 @@ export class AgentSession {
|
|
|
11947
11981
|
const estimate = branchEntries.some((entry) => entry.type === "context_edit")
|
|
11948
11982
|
? estimateProjectedContextTokens(this.sessionManager.buildSessionProjection(), branchEntries)
|
|
11949
11983
|
: estimateContextTokens(messages);
|
|
11984
|
+
// A model switch after the last reported usage invalidates that usage: it was
|
|
11985
|
+
// counted with the previous model's tokenizer, so showing it against the new
|
|
11986
|
+
// model's window misstates the context (oh-my-openagent#11684). Fall back to
|
|
11987
|
+
// the content estimate, like the compaction and context-edit paths above.
|
|
11988
|
+
if (estimate.lastUsageIndex !== null && this._isUsageStaleAfterModelChange(branchEntries)) {
|
|
11989
|
+
return { tokens: messages.reduce((sum, message) => sum + estimateTokens(message), 0), reported: false };
|
|
11990
|
+
}
|
|
11950
11991
|
return { tokens: estimate.tokens, reported: estimate.usageTokens > 0 };
|
|
11951
11992
|
}
|
|
11993
|
+
/**
|
|
11994
|
+
* Whether the latest reported usage predates the latest model switch: no
|
|
11995
|
+
* assistant message after the switch reported usage, so the last reported
|
|
11996
|
+
* figure belongs to the previous model's tokenizer. Assistant messages are
|
|
11997
|
+
* appended to the branch only once they stop streaming (a pending assistant
|
|
11998
|
+
* message is rejected), so a switch that lands mid-turn still precedes the
|
|
11999
|
+
* message entry carrying the new model's usage.
|
|
12000
|
+
*/
|
|
12001
|
+
_isUsageStaleAfterModelChange(branchEntries) {
|
|
12002
|
+
let latestModelChangeIndex = -1;
|
|
12003
|
+
for (let i = branchEntries.length - 1; i >= 0; i--) {
|
|
12004
|
+
if (branchEntries[i].type === "model_change") {
|
|
12005
|
+
latestModelChangeIndex = i;
|
|
12006
|
+
break;
|
|
12007
|
+
}
|
|
12008
|
+
}
|
|
12009
|
+
if (latestModelChangeIndex === -1)
|
|
12010
|
+
return false;
|
|
12011
|
+
for (let i = branchEntries.length - 1; i > latestModelChangeIndex; i--) {
|
|
12012
|
+
const entry = branchEntries[i];
|
|
12013
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
12014
|
+
const assistant = entry.message;
|
|
12015
|
+
if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error") {
|
|
12016
|
+
if (calculateContextTokens(assistant.usage) > 0)
|
|
12017
|
+
return false;
|
|
12018
|
+
}
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
12021
|
+
return true;
|
|
12022
|
+
}
|
|
11952
12023
|
/**
|
|
11953
12024
|
* Export session to HTML.
|
|
11954
12025
|
* @param outputPath Optional output path (defaults to session directory)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentEvent } from "@earendil-works/pi-agent-core";
|
|
2
|
+
/**
|
|
3
|
+
* Provider error text can echo a value a config reference resolved to (omo#12965). An assistant message carries that
|
|
4
|
+
* text from its first streamed partial to its final form, and the final object is the one agent state keeps and the
|
|
5
|
+
* transcript persists, so every message event is redacted in place before a listener, the transcript or the state
|
|
6
|
+
* reads it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function redactAssistantErrorText(event: AgentEvent): void;
|
|
9
|
+
//# sourceMappingURL=assistant-error-redaction.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { redactSensitiveTokenValues } from "./sensitive-output.js";
|
|
2
|
+
function redactErrorText(message) {
|
|
3
|
+
if (message.role === "assistant" && message.errorMessage) {
|
|
4
|
+
message.errorMessage = redactSensitiveTokenValues(message.errorMessage);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Provider error text can echo a value a config reference resolved to (omo#12965). An assistant message carries that
|
|
9
|
+
* text from its first streamed partial to its final form, and the final object is the one agent state keeps and the
|
|
10
|
+
* transcript persists, so every message event is redacted in place before a listener, the transcript or the state
|
|
11
|
+
* reads it.
|
|
12
|
+
*/
|
|
13
|
+
export function redactAssistantErrorText(event) {
|
|
14
|
+
if (event.type !== "message_start" && event.type !== "message_update" && event.type !== "message_end")
|
|
15
|
+
return;
|
|
16
|
+
redactErrorText(event.message);
|
|
17
|
+
if (event.type !== "message_update")
|
|
18
|
+
return;
|
|
19
|
+
const update = event.assistantMessageEvent;
|
|
20
|
+
if ("partial" in update)
|
|
21
|
+
redactErrorText(update.partial);
|
|
22
|
+
else if (update.type === "done")
|
|
23
|
+
redactErrorText(update.message);
|
|
24
|
+
else
|
|
25
|
+
redactErrorText(update.error);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=assistant-error-redaction.js.map
|
|
@@ -1,21 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved-value registry shared by every copy of this module in one JavaScript realm (omo#12965).
|
|
3
|
+
*
|
|
4
|
+
* A process can evaluate this module more than once (the bundle's shared chunk, its self-contained session-worker
|
|
5
|
+
* entry, and the SDK graph an in-process child loads), so the values are not kept in a module-local set: the first
|
|
6
|
+
* copy pins a frozen registry on `globalThis` under `SLOT_KEY` (non-writable, non-configurable, non-enumerable) and
|
|
7
|
+
* every later copy adopts it. The backing `Set` lives only in the installing copy's closure; the registry offers no
|
|
8
|
+
* delete, clear or setter, so in-realm code cannot remove a value, replace the slot or turn redaction off. Any
|
|
9
|
+
* in-realm code can `add` a string, which can only over-redact. This is a stopgap until one engine instance serves
|
|
10
|
+
* the whole process; other realms (workers, `vm` contexts, child processes) keep their own registry.
|
|
11
|
+
*
|
|
12
|
+
* Compatibility contract, permanent: the key string never changes. The v1 members `version` (1), `add(value)`,
|
|
13
|
+
* `size` (an accessor reporting the live count) and `values()` (a fresh array) are never removed or repurposed; a
|
|
14
|
+
* later contract keeps writing through this same slot and only adds members, and adopters ignore members they do not
|
|
15
|
+
* know. A copy adopts only a slot pinned the way `pin` pins it, frozen, with those four members; anything else (a
|
|
16
|
+
* slot planted before the first copy loaded, an accessor, another version) makes the import throw
|
|
17
|
+
* `ConfigReferenceRedactionSlotError`, because a copy running with a private set is the leak this registry removes.
|
|
18
|
+
*/
|
|
19
|
+
const SLOT_KEY_NAME = "@code-yeongyu/senpi/config-reference-redaction/v1";
|
|
20
|
+
const SLOT_KEY = Symbol.for(SLOT_KEY_NAME);
|
|
1
21
|
const MIN_REDACTED_LENGTH = 4;
|
|
2
|
-
|
|
3
|
-
|
|
22
|
+
/** The largest `$file` reference value (`config-reference-file.ts`); longer strings are never remembered. */
|
|
23
|
+
const MAX_REMEMBERED_LENGTH = 65_536;
|
|
24
|
+
class ConfigReferenceRedactionSlotError extends Error {
|
|
25
|
+
constructor(cause) {
|
|
26
|
+
super(`The process-wide redaction registry ${SLOT_KEY_NAME} was not installed by senpi or is incompatible; restart the process.`, cause === undefined ? undefined : { cause });
|
|
27
|
+
this.name = "ConfigReferenceRedactionSlotError";
|
|
28
|
+
this.code = "SENPI_REDACTION_SLOT_INCOMPATIBLE";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function createRegistry() {
|
|
32
|
+
const values = new Set();
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
version: 1,
|
|
35
|
+
add(value) {
|
|
36
|
+
if (typeof value !== "string" || value.length < MIN_REDACTED_LENGTH || value.length > MAX_REMEMBERED_LENGTH)
|
|
37
|
+
return;
|
|
38
|
+
values.add(value);
|
|
39
|
+
},
|
|
40
|
+
get size() {
|
|
41
|
+
return values.size;
|
|
42
|
+
},
|
|
43
|
+
values: () => [...values],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function isFrozenV1Registry(value) {
|
|
47
|
+
return (typeof value === "object" &&
|
|
48
|
+
value !== null &&
|
|
49
|
+
Object.isFrozen(value) &&
|
|
50
|
+
Reflect.get(value, "version") === 1 &&
|
|
51
|
+
typeof Reflect.get(value, "add") === "function" &&
|
|
52
|
+
typeof Reflect.get(value, "values") === "function" &&
|
|
53
|
+
typeof Reflect.get(value, "size") === "number");
|
|
54
|
+
}
|
|
55
|
+
function pin(registry) {
|
|
56
|
+
try {
|
|
57
|
+
Object.defineProperty(globalThis, SLOT_KEY, {
|
|
58
|
+
value: registry,
|
|
59
|
+
writable: false,
|
|
60
|
+
configurable: false,
|
|
61
|
+
enumerable: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new ConfigReferenceRedactionSlotError(error);
|
|
66
|
+
}
|
|
67
|
+
return registry;
|
|
68
|
+
}
|
|
69
|
+
function sharedRegistry() {
|
|
70
|
+
const slot = Reflect.getOwnPropertyDescriptor(globalThis, SLOT_KEY);
|
|
71
|
+
if (slot === undefined)
|
|
72
|
+
return pin(createRegistry());
|
|
73
|
+
const value = slot.value;
|
|
74
|
+
if (slot.writable !== false || slot.configurable !== false || !isFrozenV1Registry(value)) {
|
|
75
|
+
throw new ConfigReferenceRedactionSlotError();
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
const registry = sharedRegistry();
|
|
80
|
+
let redactionForms = [];
|
|
81
|
+
let formsSize = 0;
|
|
4
82
|
/** Every value a reference resolves to is kept for the process lifetime, including rotated ones: no cap, no eviction. */
|
|
5
83
|
export function rememberResolvedReferenceValue(value) {
|
|
6
|
-
|
|
7
|
-
return;
|
|
8
|
-
resolvedValues.add(value);
|
|
9
|
-
redactionForms = undefined;
|
|
84
|
+
registry.add(value);
|
|
10
85
|
}
|
|
11
86
|
function encodings(value) {
|
|
12
87
|
const form = new URLSearchParams([["", value]]).toString().slice(1);
|
|
13
88
|
return [value, JSON.stringify(value).slice(1, -1), encodeURIComponent(value), encodeURI(value), form];
|
|
14
89
|
}
|
|
90
|
+
/** The registry is append-only, so a changed size is the only way its content can change. */
|
|
15
91
|
function forms() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
92
|
+
const size = registry.size;
|
|
93
|
+
if (size !== formsSize) {
|
|
94
|
+
redactionForms = [...new Set(registry.values().flatMap(encodings))]
|
|
95
|
+
.filter((form) => form.length >= MIN_REDACTED_LENGTH)
|
|
96
|
+
.sort((left, right) => right.length - left.length);
|
|
97
|
+
formsSize = size;
|
|
98
|
+
}
|
|
19
99
|
return redactionForms;
|
|
20
100
|
}
|
|
21
101
|
/** Replaces every resolved reference value, raw or encoded, before text reaches a sink outside the process. */
|
|
@@ -134,7 +214,7 @@ function isProtocolValue(key, value) {
|
|
|
134
214
|
return Object.hasOwn(PROTOCOL_VALUES, key) && PROTOCOL_VALUES[key]?.has(value) === true;
|
|
135
215
|
}
|
|
136
216
|
export function redactResolvedReferenceData(value) {
|
|
137
|
-
if (
|
|
217
|
+
if (registry.size === 0)
|
|
138
218
|
return value;
|
|
139
219
|
const copies = new WeakMap();
|
|
140
220
|
const pending = [];
|
|
@@ -16,6 +16,16 @@ export type ContextFiles = ContextFile[] & {
|
|
|
16
16
|
export interface ContextFileLimits {
|
|
17
17
|
maxBytesPerFile?: number;
|
|
18
18
|
maxTotalBytes?: number;
|
|
19
|
+
/**
|
|
20
|
+
* The global (user-scope) context file to load instead of the agent-dir probe (senpi#5567).
|
|
21
|
+
* Honored from the user's own settings only (`SettingsManager.getGlobalContextFilePath`); a
|
|
22
|
+
* project's settings never set it. A leading `~` is expanded; a relative path resolves against
|
|
23
|
+
* the agent dir, like other paths in global settings; a directory runs the same candidate probe
|
|
24
|
+
* as the agent dir. Unset or blank keeps the agent-dir file. A path that is set but missing,
|
|
25
|
+
* unreadable, a special file, or a directory with no candidate falls back to the agent-dir probe
|
|
26
|
+
* and reports one warning diagnostic instead of erroring.
|
|
27
|
+
*/
|
|
28
|
+
globalPath?: string;
|
|
19
29
|
}
|
|
20
30
|
export declare const DEFAULT_CONTEXT_FILE_MAX_BYTES: number;
|
|
21
31
|
export declare const DEFAULT_CONTEXT_FILES_MAX_TOTAL_BYTES: number;
|
|
@@ -29,6 +29,47 @@ function loadContextFileFromDir(dir) {
|
|
|
29
29
|
}
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The global user-scope context file (senpi#5567). `contextFiles.globalPath` names a file (or a
|
|
34
|
+
* directory to probe for the usual candidates) to load in place of the agent-dir file. Precedence
|
|
35
|
+
* is defined and never errors: a blank or unset path keeps the agent-dir probe silently; a
|
|
36
|
+
* configured target that is missing, unreadable, a special file, or a directory with no candidates
|
|
37
|
+
* falls back to the agent-dir probe and returns one warning naming the configured path.
|
|
38
|
+
*/
|
|
39
|
+
function loadGlobalContextFile(agentDir, globalPath) {
|
|
40
|
+
const explicit = typeof globalPath === "string" ? globalPath.trim() : "";
|
|
41
|
+
if (explicit === "")
|
|
42
|
+
return { file: loadContextFileFromDir(agentDir) };
|
|
43
|
+
const resolved = resolvePath(explicit, agentDir);
|
|
44
|
+
const fallBack = (problem) => ({
|
|
45
|
+
file: loadContextFileFromDir(agentDir),
|
|
46
|
+
diagnostic: {
|
|
47
|
+
type: "warning",
|
|
48
|
+
path: resolved,
|
|
49
|
+
message: `contextFiles.globalPath ${problem}; using the global context file from ${agentDir} instead.`,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
let stats;
|
|
53
|
+
try {
|
|
54
|
+
stats = statSync(resolved);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const code = error.code;
|
|
58
|
+
return fallBack(code === "ENOENT" || code === "ENOTDIR" ? "does not exist" : `could not be read (${String(error)})`);
|
|
59
|
+
}
|
|
60
|
+
if (stats.isDirectory()) {
|
|
61
|
+
const file = loadContextFileFromDir(resolved);
|
|
62
|
+
return file ? { file } : fallBack("is a directory with no AGENTS.md or CLAUDE.md");
|
|
63
|
+
}
|
|
64
|
+
if (!stats.isFile())
|
|
65
|
+
return fallBack("is not a regular file or directory");
|
|
66
|
+
try {
|
|
67
|
+
return { file: { path: resolved, content: stripBom(readFileSync(resolved, "utf-8")) } };
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return fallBack(`could not be read (${String(error)})`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
32
73
|
/**
|
|
33
74
|
* The main repo's context file that a nested linked worktree's own copy shadows: both
|
|
34
75
|
* occupy the same logical repository scope, so loading both applies that context twice. Returns
|
|
@@ -142,21 +183,22 @@ function applyContextFileLimits(files, limits) {
|
|
|
142
183
|
}
|
|
143
184
|
return Object.assign(kept, { diagnostics });
|
|
144
185
|
}
|
|
145
|
-
/** Compares directories by their real path, ignoring case where the filesystem does (Windows). */
|
|
146
|
-
function
|
|
147
|
-
const real = canonicalizePath(resolvePath(
|
|
186
|
+
/** Compares files and directories by their real path, ignoring case where the filesystem does (Windows). */
|
|
187
|
+
function pathKey(path) {
|
|
188
|
+
const real = canonicalizePath(resolvePath(path));
|
|
148
189
|
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
149
190
|
}
|
|
150
191
|
export function loadProjectContextFiles(options) {
|
|
151
192
|
const resolvedCwd = resolvePath(options.cwd);
|
|
152
193
|
const resolvedAgentDir = resolvePath(options.agentDir);
|
|
153
|
-
const homeKey =
|
|
194
|
+
const homeKey = pathKey(options.homeDir ?? homedir());
|
|
154
195
|
const contextFiles = [];
|
|
196
|
+
// Real-path keys: a symlinked ancestor file that points at an already loaded file is the same file.
|
|
155
197
|
const seenPaths = new Set();
|
|
156
|
-
const globalContext =
|
|
157
|
-
if (globalContext) {
|
|
158
|
-
contextFiles.push({ ...globalContext, scope: "user" });
|
|
159
|
-
seenPaths.add(globalContext.path);
|
|
198
|
+
const globalContext = loadGlobalContextFile(resolvedAgentDir, options.globalPath);
|
|
199
|
+
if (globalContext.file) {
|
|
200
|
+
contextFiles.push({ ...globalContext.file, scope: "user" });
|
|
201
|
+
seenPaths.add(pathKey(globalContext.file.path));
|
|
160
202
|
}
|
|
161
203
|
const ancestorContextFiles = [];
|
|
162
204
|
const shadowedContextFile = findShadowedContextFile(resolvedCwd);
|
|
@@ -164,9 +206,10 @@ export function loadProjectContextFiles(options) {
|
|
|
164
206
|
while (true) {
|
|
165
207
|
const contextFile = loadContextFileFromDir(currentDir);
|
|
166
208
|
const isShadowed = shadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? "") === shadowedContextFile;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
209
|
+
const contextFileKey = contextFile ? pathKey(contextFile.path) : undefined;
|
|
210
|
+
if (contextFile && contextFileKey !== undefined && !isShadowed && !seenPaths.has(contextFileKey)) {
|
|
211
|
+
ancestorContextFiles.unshift(pathKey(currentDir) === homeKey ? { ...contextFile, scope: "user" } : contextFile);
|
|
212
|
+
seenPaths.add(contextFileKey);
|
|
170
213
|
}
|
|
171
214
|
const parentDir = dirname(currentDir);
|
|
172
215
|
if (parentDir === currentDir)
|
|
@@ -174,6 +217,9 @@ export function loadProjectContextFiles(options) {
|
|
|
174
217
|
currentDir = parentDir;
|
|
175
218
|
}
|
|
176
219
|
contextFiles.push(...ancestorContextFiles);
|
|
177
|
-
|
|
220
|
+
const limited = applyContextFileLimits(contextFiles, options);
|
|
221
|
+
if (globalContext.diagnostic)
|
|
222
|
+
limited.diagnostics.unshift(globalContext.diagnostic);
|
|
223
|
+
return limited;
|
|
178
224
|
}
|
|
179
225
|
//# sourceMappingURL=context-files.js.map
|
|
@@ -168,11 +168,24 @@ export interface HookModelRequest {
|
|
|
168
168
|
readonly claim: SignedPlacementClaim;
|
|
169
169
|
readonly input: HookModelRequestInput;
|
|
170
170
|
}
|
|
171
|
+
/** A user hook's failure on the hand: reported with the reply, as a fused session reports it, never thrown. */
|
|
172
|
+
export interface HookFailure {
|
|
173
|
+
readonly extensionPath: string;
|
|
174
|
+
readonly event: string;
|
|
175
|
+
readonly error: string;
|
|
176
|
+
}
|
|
171
177
|
export interface HookModelRequestReply {
|
|
172
178
|
readonly t: "hook.model_request.reply";
|
|
173
179
|
readonly sessionId: string;
|
|
174
180
|
readonly stepId: string;
|
|
175
181
|
readonly output: HookModelRequestInput;
|
|
182
|
+
/** The hooks that failed; `output` is what the others produced. Absent: none failed. */
|
|
183
|
+
readonly errors?: readonly HookFailure[];
|
|
184
|
+
/** The messages and notices the hooks sent during the call, as on every loop-hook reply (S33). Absent: none. */
|
|
185
|
+
readonly sent?: readonly WireJson[];
|
|
186
|
+
readonly notices?: readonly WireJson[];
|
|
187
|
+
/** Late messages and notices the hand dropped past its per-session bound. Absent: none. */
|
|
188
|
+
readonly droppedLate?: number;
|
|
176
189
|
}
|
|
177
190
|
export interface HandSessionAdvertisement {
|
|
178
191
|
readonly t: "hand.session.advertise";
|
|
@@ -7,8 +7,12 @@ export function groupChangedPaths(paths, targets, rearmedContainers) {
|
|
|
7
7
|
if (!targetMatchesPath(activeTarget.target, path))
|
|
8
8
|
continue;
|
|
9
9
|
matched = true;
|
|
10
|
-
//
|
|
11
|
-
|
|
10
|
+
// Discovery containers only rearm their watches; discovered files carry the change.
|
|
11
|
+
// An extensions directory may contain only runtime state (e.g. a park's goal/no-session).
|
|
12
|
+
if (rearmedContainers.includes(resolve(path)) &&
|
|
13
|
+
(activeTarget.target.id === "builtin-project-presence" ||
|
|
14
|
+
activeTarget.target.id === "builtin-global-extensions" ||
|
|
15
|
+
activeTarget.target.id === "builtin-project-extensions"))
|
|
12
16
|
continue;
|
|
13
17
|
const group = groups.get(activeTarget.registrationId) ?? [];
|
|
14
18
|
if (!group.includes(path))
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { Check } from "typebox/value";
|
|
3
5
|
import { keyedIdentity } from "../config-identity-key.js";
|
|
4
6
|
import { resolveAuthMode } from "./context.js";
|
|
7
|
+
import { oauthGrantIdentity } from "./oauth-grant.js";
|
|
5
8
|
import { McpTokenStore } from "./token-store.js";
|
|
6
9
|
/** Local snapshot only: never migrates credentials, acquires a lock, or refreshes authentication. */
|
|
7
10
|
export function mcpCredentialIdentity(config, serverName, agentDir, env) {
|
|
8
11
|
const material = credentialMaterial(config, serverName, agentDir, env);
|
|
9
|
-
return material === undefined || material === "none"
|
|
12
|
+
return material === undefined || material === "none" || resolveAuthMode(config) === "oauth"
|
|
13
|
+
? material
|
|
14
|
+
: keyedIdentity(agentDir, material);
|
|
10
15
|
}
|
|
11
16
|
/** The unkeyed identity earlier builds persisted; read only to adopt a cache entry for the same credential. */
|
|
12
17
|
export function legacyMcpCredentialIdentity(config, serverName, agentDir, env) {
|
|
18
|
+
if (resolveAuthMode(config) === "oauth")
|
|
19
|
+
return undefined;
|
|
13
20
|
const material = credentialMaterial(config, serverName, agentDir, env);
|
|
14
21
|
return material === undefined || material === "none"
|
|
15
22
|
? material
|
|
@@ -31,14 +38,15 @@ function credentialMaterial(config, serverName, agentDir, env) {
|
|
|
31
38
|
try {
|
|
32
39
|
const store = new McpTokenStore({ serverName, serverUrl: config.url ?? "", agentDir });
|
|
33
40
|
const value = JSON.parse(readFileSync(store.tokensPath, "utf8"));
|
|
34
|
-
if (
|
|
35
|
-
return undefined;
|
|
36
|
-
if (typeof value.accessToken !== "string" || value.accessToken.length === 0)
|
|
37
|
-
return undefined;
|
|
38
|
-
const refreshToken = "refreshToken" in value ? value.refreshToken : undefined;
|
|
39
|
-
if (refreshToken !== undefined && typeof refreshToken !== "string")
|
|
41
|
+
if (!Check(storedGrantSchema, value))
|
|
40
42
|
return undefined;
|
|
41
|
-
return
|
|
43
|
+
return oauthGrantIdentity(value, {
|
|
44
|
+
serverName,
|
|
45
|
+
serverUrl: store.serverUrl,
|
|
46
|
+
agentDir: store.agentDir,
|
|
47
|
+
clientId: config.oauth?.clientId,
|
|
48
|
+
clientMetadataUrl: config.oauth?.clientMetadataUrl,
|
|
49
|
+
});
|
|
42
50
|
}
|
|
43
51
|
catch (error) {
|
|
44
52
|
if (error instanceof Error)
|
|
@@ -46,4 +54,20 @@ function credentialMaterial(config, serverName, agentDir, env) {
|
|
|
46
54
|
throw error;
|
|
47
55
|
}
|
|
48
56
|
}
|
|
57
|
+
// Parse exactly the stored fields the identity reader consumes; authorization is parsed by its proof owner.
|
|
58
|
+
const storedGrantSchema = Type.Object({
|
|
59
|
+
accessToken: Type.String({ minLength: 1 }),
|
|
60
|
+
refreshToken: Type.Optional(Type.String()),
|
|
61
|
+
issuer: Type.Optional(Type.String()),
|
|
62
|
+
resource: Type.Optional(Type.String()),
|
|
63
|
+
scope: Type.Optional(Type.String()),
|
|
64
|
+
refreshScope: Type.Optional(Type.String()),
|
|
65
|
+
clientInfo: Type.Optional(Type.Object({
|
|
66
|
+
client_id: Type.String(),
|
|
67
|
+
client_secret: Type.Optional(Type.String()),
|
|
68
|
+
token_endpoint_auth_method: Type.Optional(Type.String()),
|
|
69
|
+
issuer: Type.Optional(Type.String()),
|
|
70
|
+
})),
|
|
71
|
+
discoveryState: Type.Optional(Type.Object({ authorizationServerUrl: Type.String() })),
|
|
72
|
+
});
|
|
49
73
|
//# sourceMappingURL=catalog-identity.js.map
|