@code-yeongyu/senpi 2026.10.21-1 → 2026.10.21-2
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 +33 -0
- package/dist/bundle/chunks/{anthropic-messages-S5BTP7U2.js → anthropic-messages-2IQD75YN.js} +1 -1
- package/dist/bundle/chunks/args-SV2KM4KA.js +2 -0
- package/dist/bundle/chunks/azure-openai-responses-HFI6SDH5.js +2 -0
- package/dist/bundle/chunks/{chunk-WQLMQGOW.js → chunk-2AJB5S4C.js} +1 -1
- package/dist/bundle/chunks/{chunk-XSRM367N.js → chunk-3AK3WUN4.js} +2 -2
- package/dist/bundle/chunks/{chunk-6TL72TUD.js → chunk-4HJTPWZY.js} +1 -1
- package/dist/bundle/chunks/chunk-4NPBDGC4.js +26 -0
- package/dist/bundle/chunks/{chunk-E4WOCULF.js → chunk-4XR5HDWC.js} +72 -69
- package/dist/bundle/chunks/{chunk-6IWSMXBA.js → chunk-5GC4IQ3A.js} +1 -1
- package/dist/bundle/chunks/{chunk-7I7ZMSBF.js → chunk-A4B4CMOR.js} +1 -1
- package/dist/bundle/chunks/chunk-B4N4N7BR.js +8 -0
- package/dist/bundle/chunks/{chunk-X23XXKMH.js → chunk-BVFJAXWO.js} +1 -1
- package/dist/bundle/chunks/{chunk-25B6JQC4.js → chunk-C3BJRXEV.js} +1 -1
- package/dist/bundle/chunks/{chunk-DKBJM22C.js → chunk-CQDU6S6C.js} +2 -2
- package/dist/bundle/chunks/{chunk-MF57DS3V.js → chunk-EV4TWAJI.js} +1 -1
- package/dist/bundle/chunks/{chunk-JJXDWX46.js → chunk-F3JGTOLH.js} +1 -1
- package/dist/bundle/chunks/{chunk-CRY5CP3B.js → chunk-G42JHX5Q.js} +3 -3
- package/dist/bundle/chunks/{chunk-5VRI3CME.js → chunk-G7VYQRE7.js} +2 -2
- package/dist/bundle/chunks/chunk-GVSBCJFI.js +2 -0
- package/dist/bundle/chunks/{chunk-KUZPBZUK.js → chunk-HKDGQE6D.js} +2 -2
- package/dist/bundle/chunks/{chunk-HD67U37N.js → chunk-HRV7NEOJ.js} +1 -1
- package/dist/bundle/chunks/chunk-JDETIKL7.js +7 -0
- package/dist/bundle/chunks/{chunk-MVNVDGYF.js → chunk-JGMG227A.js} +1 -1
- package/dist/bundle/chunks/chunk-MHD7SSHF.js +2 -0
- package/dist/bundle/chunks/{chunk-CEQMTVYS.js → chunk-MXLEWTWC.js} +2 -2
- package/dist/bundle/chunks/{chunk-C5W3D56I.js → chunk-N6UN2SDS.js} +3 -3
- package/dist/bundle/chunks/chunk-NTK2MHJ5.js +2 -0
- package/dist/bundle/chunks/chunk-OMN4QWFA.js +3 -0
- package/dist/bundle/chunks/chunk-PLQ5WRXN.js +2 -0
- package/dist/bundle/chunks/{chunk-2MOGCBPT.js → chunk-PUMCQ5HS.js} +1 -1
- package/dist/bundle/chunks/{chunk-55YTTFNY.js → chunk-PUQFKAB4.js} +1 -1
- package/dist/bundle/chunks/{chunk-IJT3GSB2.js → chunk-SN7PTZTC.js} +1 -1
- package/dist/bundle/chunks/{chunk-FWHSGCWR.js → chunk-T2IIUI3J.js} +3 -3
- package/dist/bundle/chunks/{chunk-5AOKDC6G.js → chunk-TSETMVJP.js} +1 -1
- package/dist/bundle/chunks/{chunk-SR7DBNWN.js → chunk-V6AYNGMX.js} +1 -1
- package/dist/bundle/chunks/chunk-VDPJ7W52.js +11 -0
- package/dist/bundle/chunks/{chunk-EPQK674G.js → chunk-VLKULV6F.js} +1 -1
- package/dist/bundle/chunks/{chunk-SL7TSFR5.js → chunk-VXACHCLB.js} +1 -1
- package/dist/bundle/chunks/{chunk-FM7NDZR5.js → chunk-VYSRUDEU.js} +1 -1
- package/dist/bundle/chunks/chunk-YMFGBSO3.js +2 -0
- package/dist/bundle/chunks/cli-main-RA6EBOOU.js +3 -0
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-4QEQ7MGK.js → cloudflare-workers-ai-system-one-BVENEDT6.js} +1 -1
- package/dist/bundle/chunks/cursor-agent.js +3 -3
- package/dist/bundle/chunks/google-generative-ai-ONS2KSGN.js +2 -0
- package/dist/bundle/chunks/google-vertex-5VS7ZESH.js +2 -0
- package/dist/bundle/chunks/help-fast-path-FZY53UXB.js +2 -0
- package/dist/bundle/chunks/host-command-TNUIHEFP.js +2 -0
- package/dist/bundle/chunks/{host-lifecycle-EZQ5VCQV.js → host-lifecycle-LDOHAYLK.js} +1 -1
- package/dist/bundle/chunks/host-session-command-OKASUQPA.js +20 -0
- package/dist/bundle/chunks/interactive-mode-PSGTSURY.js +2 -0
- package/dist/bundle/chunks/{mistral-conversations-5EHLE7PZ.js → mistral-conversations-RLDMF2BI.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-OMJZ5VXM.js → multi-session-host-J5BVVUG3.js} +7 -6
- package/dist/bundle/chunks/openai-codex-responses-VRIJIZJG.js +10 -0
- package/dist/bundle/chunks/{openai-completions-NHDMDAS7.js → openai-completions-EZBOIXHP.js} +2 -2
- package/dist/bundle/chunks/{openai-images-PNXQDBSP.js → openai-images-KKM343F2.js} +1 -1
- package/dist/bundle/chunks/openai-responses-CMBH3DCY.js +3 -0
- package/dist/bundle/chunks/{package-manager-cli-7GQDB5DX.js → package-manager-cli-JHVRG35I.js} +1 -1
- package/dist/bundle/chunks/{role-WQWYL4ZD.js → role-LNF5MFGC.js} +1 -1
- package/dist/bundle/chunks/rotation-stream-JK3Q4IWI.js +2 -0
- package/dist/bundle/chunks/rpc-mode-HVSPPRTT.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-FI7H7LGY.js → rpc-monitor-source-2DQZLFFJ.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-IQAKNRTV.js → runtime-session-CLNODAYE.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-XXJC4QQN.js → schedule-command-OMI4VWUV.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-UPYF4OFP.js +3 -0
- package/dist/bundle/chunks/session-picker-NR7CRVNQ.js +2 -0
- package/dist/bundle/chunks/session-worker.js +143 -139
- package/dist/bundle/chunks/{typesafe-system-one-W2ZTYF7X.js → typesafe-system-one-PJIWA5SP.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli/host-command.d.ts +2 -0
- package/dist/cli/host-command.js +14 -19
- package/dist/cli/host-handover-args.d.ts +4 -0
- package/dist/cli/host-handover-args.js +20 -0
- package/dist/core/agent-session.d.ts +29 -1
- package/dist/core/agent-session.js +74 -19
- package/dist/core/auth-storage.d.ts +44 -1
- package/dist/core/auth-storage.js +120 -3
- package/dist/core/client-message-identity.d.ts +3 -0
- package/dist/core/credential-accounts.d.ts +2 -0
- package/dist/core/credential-accounts.js +8 -1
- package/dist/core/credential-pool/rotation-slots.js +3 -2
- package/dist/core/extensions/builtin/account/index.js +6 -1
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +2 -1
- package/dist/core/extensions/builtin/compaction/openai-remote.js +6 -5
- package/dist/core/extensions/builtin/goal/provider-retry.d.ts +2 -2
- package/dist/core/extensions/builtin/goal/provider-retry.js +18 -6
- package/dist/core/extensions/builtin/terminal/command-monitor-source.d.ts +17 -4
- package/dist/core/extensions/builtin/terminal/command-monitor-source.js +33 -6
- package/dist/core/extensions/builtin/terminal/durable-command.js +1 -0
- package/dist/core/extensions/builtin/terminal/extension.js +28 -0
- package/dist/core/extensions/builtin/terminal/monitor-notify-details.d.ts +6 -0
- package/dist/core/extensions/builtin/terminal/monitor-notify-details.js +19 -0
- package/dist/core/extensions/builtin/terminal/monitor-notify-format.d.ts +18 -0
- package/dist/core/extensions/builtin/terminal/monitor-notify-format.js +52 -0
- package/dist/core/extensions/builtin/terminal/monitor-notify.d.ts +8 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.js +65 -119
- package/dist/core/extensions/builtin/terminal/monitor-overflow-file.d.ts +7 -0
- package/dist/core/extensions/builtin/terminal/monitor-overflow-file.js +67 -0
- package/dist/core/extensions/builtin/terminal/monitor-overflow.d.ts +37 -0
- package/dist/core/extensions/builtin/terminal/monitor-overflow.js +75 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +10 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +24 -1
- package/dist/core/extensions/builtin/terminal/monitor-types.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/monitor-wake-fit.d.ts +9 -0
- package/dist/core/extensions/builtin/terminal/monitor-wake-fit.js +34 -0
- package/dist/core/extensions/builtin/terminal/monitor-wake-pacing.d.ts +10 -0
- package/dist/core/extensions/builtin/terminal/monitor-wake-pacing.js +41 -0
- package/dist/core/extensions/builtin/terminal/restore-session.js +2 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-observer.js +5 -1
- package/dist/core/extensions/builtin/terminal/settings.d.ts +10 -0
- package/dist/core/extensions/builtin/terminal/settings.js +13 -1
- package/dist/core/extensions/builtin/terminal/shared.d.ts +17 -10
- package/dist/core/extensions/builtin/terminal/shared.js +19 -10
- package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +3 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +7 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest.js +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-schema.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-schema.js +6 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor.js +9 -1
- package/dist/core/extensions/builtin/tps.js +1 -8
- package/dist/core/extensions/loader.js +3 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.js +12 -1
- package/dist/core/extensions/types.d.ts +3 -1
- package/dist/core/session-manager.d.ts +10 -1
- package/dist/core/session-manager.js +4 -3
- package/dist/core/terminal-settings.d.ts +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/main.d.ts +14 -0
- package/dist/main.js +25 -8
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.js +1 -0
- package/dist/modes/interactive/components/footer.js +4 -2
- package/dist/modes/interactive/interactive-mode.d.ts +5 -0
- package/dist/modes/interactive/interactive-mode.js +13 -1
- package/dist/modes/rpc/client-admission-record.d.ts +5 -0
- package/dist/modes/rpc/client-admission-record.js +2 -0
- package/dist/modes/rpc/connection-handler.d.ts +3 -0
- package/dist/modes/rpc/connection-handler.js +33 -5
- package/dist/modes/rpc/connection-roles.d.ts +30 -0
- package/dist/modes/rpc/connection-roles.js +53 -0
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/host-capabilities.js +2 -1
- package/dist/modes/rpc/host-decision.d.ts +4 -1
- package/dist/modes/rpc/host-decision.js +6 -3
- package/dist/modes/rpc/host-ensure-liveness.d.ts +11 -0
- package/dist/modes/rpc/host-ensure-liveness.js +26 -0
- package/dist/modes/rpc/host-ensure.js +2 -3
- package/dist/modes/rpc/host-holder.d.ts +27 -0
- package/dist/modes/rpc/host-holder.js +18 -0
- package/dist/modes/rpc/host-runner.d.ts +2 -0
- package/dist/modes/rpc/host-runner.js +8 -1
- package/dist/modes/rpc/host-status-all.js +1 -0
- package/dist/modes/rpc/host-status.d.ts +1 -0
- package/dist/modes/rpc/host-status.js +5 -0
- package/dist/modes/rpc/host-stop-generation.d.ts +22 -0
- package/dist/modes/rpc/host-stop-generation.js +36 -0
- package/dist/modes/rpc/host-stop-start-time.d.ts +15 -0
- package/dist/modes/rpc/host-stop-start-time.js +30 -0
- package/dist/modes/rpc/host-stop.d.ts +13 -1
- package/dist/modes/rpc/host-stop.js +78 -9
- package/dist/modes/rpc/rpc-client.d.ts +9 -0
- package/dist/modes/rpc/rpc-client.js +15 -0
- package/dist/modes/rpc/rpc-types.d.ts +18 -0
- package/dist/modes/rpc/rpc-types.js +2 -0
- package/dist/modes/rpc/session-binding.d.ts +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +1 -0
- package/dist/modes/rpc/session-command-router.js +13 -0
- package/dist/modes/rpc/session-worker-client.d.ts +1 -1
- package/dist/modes/rpc/session-worker-client.js +12 -1
- package/dist/modes/rpc/session-worker-protocol.d.ts +3 -0
- package/dist/modes/rpc/session-worker.js +9 -0
- package/docs/rpc.md +54 -2
- package/docs/session-format.md +5 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/args-CRXNC7IS.js +0 -2
- package/dist/bundle/chunks/azure-openai-responses-2DCY6QKO.js +0 -2
- package/dist/bundle/chunks/chunk-5ZBDN3LB.js +0 -26
- package/dist/bundle/chunks/chunk-AEJUOQ7U.js +0 -2
- package/dist/bundle/chunks/chunk-LAWS3FYQ.js +0 -11
- package/dist/bundle/chunks/chunk-LM76EMEJ.js +0 -7
- package/dist/bundle/chunks/chunk-M237EDHB.js +0 -2
- package/dist/bundle/chunks/chunk-V6RY5A6T.js +0 -2
- package/dist/bundle/chunks/chunk-WIAXG52U.js +0 -2
- package/dist/bundle/chunks/chunk-WTFD4TII.js +0 -8
- package/dist/bundle/chunks/chunk-Z33PKFXM.js +0 -3
- package/dist/bundle/chunks/cli-main-RKC5Z25H.js +0 -3
- package/dist/bundle/chunks/google-generative-ai-3SKUSCIL.js +0 -2
- package/dist/bundle/chunks/google-vertex-YHSIIE4G.js +0 -2
- package/dist/bundle/chunks/help-fast-path-FWS5ARO5.js +0 -2
- package/dist/bundle/chunks/host-command-L66Y7ZL5.js +0 -2
- package/dist/bundle/chunks/host-session-command-2YNQNTEH.js +0 -20
- package/dist/bundle/chunks/interactive-mode-XOKTNBLA.js +0 -2
- package/dist/bundle/chunks/openai-codex-responses-4BIPACKV.js +0 -10
- package/dist/bundle/chunks/openai-responses-2UOPEJLP.js +0 -3
- package/dist/bundle/chunks/rotation-stream-J2NUPRMG.js +0 -2
- package/dist/bundle/chunks/rpc-mode-WM5WH7TH.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-MTOAYRRY.js +0 -3
- package/dist/bundle/chunks/session-picker-LU7BPLNI.js +0 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { convertResponsesMessages, getContextProvenance, normalizeContext, } from "@earendil-works/pi-ai";
|
|
2
|
+
import { deriveOpenAIPromptCacheKey } from "@earendil-works/pi-ai/api/openai-prompt-cache";
|
|
2
3
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
3
4
|
import { convertToLlm } from "../../../messages.js";
|
|
4
5
|
import { buildContextEntries, buildSessionContext, getSessionContextEntryId, SESSION_CONTEXT_ENTRY_ID, sessionEntryToContextMessages, } from "../../../session-manager.js";
|
|
@@ -25,11 +26,12 @@ export function createOpenAiRemoteCompactionRequest(options) {
|
|
|
25
26
|
if (input.length === 0)
|
|
26
27
|
return undefined;
|
|
27
28
|
return {
|
|
29
|
+
sessionId: options.sessionId,
|
|
28
30
|
body: {
|
|
29
31
|
model: options.model.id,
|
|
30
32
|
input,
|
|
31
33
|
...(options.systemPrompt ? { instructions: options.systemPrompt } : {}),
|
|
32
|
-
|
|
34
|
+
prompt_cache_key: deriveOpenAIPromptCacheKey(options.model.id, normalizeContext({ systemPrompt: options.systemPrompt, messages: [] })),
|
|
33
35
|
...(options.serviceTier ? { service_tier: options.serviceTier } : {}),
|
|
34
36
|
},
|
|
35
37
|
inputItemCount: input.length,
|
|
@@ -98,7 +100,6 @@ export function createOpenAiResponsesStreamCompactionPayload(payload, request) {
|
|
|
98
100
|
...payload,
|
|
99
101
|
model: request.body.model,
|
|
100
102
|
input: [...leadingPromptMessages(payload.input), ...createOpenAiResponsesStreamCompactionInput(request)],
|
|
101
|
-
...(request.body.prompt_cache_key ? { prompt_cache_key: request.body.prompt_cache_key } : {}),
|
|
102
103
|
...(request.body.service_tier ? { service_tier: request.body.service_tier } : {}),
|
|
103
104
|
};
|
|
104
105
|
}
|
|
@@ -173,7 +174,7 @@ async function runOpenAiResponsesStreamCompaction(options) {
|
|
|
173
174
|
}
|
|
174
175
|
return transformedPayload;
|
|
175
176
|
},
|
|
176
|
-
sessionId: options.request.
|
|
177
|
+
sessionId: options.request.sessionId,
|
|
177
178
|
signal: options.signal,
|
|
178
179
|
transport: "websocket",
|
|
179
180
|
});
|
|
@@ -361,7 +362,7 @@ export async function runOpenAiRemoteCompaction(ctx, event, emit, dependencies =
|
|
|
361
362
|
branchEntries: event.branchEntries,
|
|
362
363
|
messages: providerRequest?.messages,
|
|
363
364
|
tokensBefore: event.preparation.tokensBefore,
|
|
364
|
-
|
|
365
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
365
366
|
serviceTier,
|
|
366
367
|
});
|
|
367
368
|
if (!request) {
|
|
@@ -403,7 +404,7 @@ export async function runOpenAiRemoteCompaction(ctx, event, emit, dependencies =
|
|
|
403
404
|
const transformedHeaders = providerRequest
|
|
404
405
|
? await providerRequest.transformHeaders(auth.headers ?? {})
|
|
405
406
|
: auth.headers;
|
|
406
|
-
const requestHeaders = createOpenAiRemoteCompactionHeaders(requestModel, { ...auth, headers: transformedHeaders }, request.
|
|
407
|
+
const requestHeaders = createOpenAiRemoteCompactionHeaders(requestModel, { ...auth, headers: transformedHeaders }, request.sessionId);
|
|
407
408
|
if (!requestHeaders) {
|
|
408
409
|
emit?.({
|
|
409
410
|
version: 1,
|
|
@@ -3,8 +3,8 @@ export declare const PROVIDER_RETRY_BASE_MS = 15000;
|
|
|
3
3
|
export declare const PROVIDER_RETRY_CAP_MS: number;
|
|
4
4
|
export declare const PROVIDER_RETRY_WAKE_CAP_MS: number;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Prefer a provider's structured usage reset, then its retry-after marker. Legacy
|
|
7
|
+
* pool guidance falls back to the earliest future ISO instant in the error only.
|
|
8
8
|
*/
|
|
9
9
|
export declare function providerRetryAt(messages: readonly AgentMessage[], now: number): number | undefined;
|
|
10
10
|
/**
|
|
@@ -1,18 +1,30 @@
|
|
|
1
|
+
import { parseRetryAfterMsMarker } from "@earendil-works/pi-ai/utils/retry-hint";
|
|
1
2
|
import { lastAssistantMessage } from "./last-assistant-message.js";
|
|
2
3
|
export const PROVIDER_RETRY_BASE_MS = 15_000;
|
|
3
4
|
export const PROVIDER_RETRY_CAP_MS = 30 * 60_000;
|
|
4
5
|
export const PROVIDER_RETRY_WAKE_CAP_MS = 6 * 60 * 60_000;
|
|
5
6
|
const WAKE_MARGIN_MS = 1_000;
|
|
6
7
|
const ISO_INSTANT = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\b/g;
|
|
7
|
-
function failureText(messages) {
|
|
8
|
-
return lastAssistantMessage(messages)?.errorMessage ?? "";
|
|
9
|
-
}
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Prefer a provider's structured usage reset, then its retry-after marker. Legacy
|
|
10
|
+
* pool guidance falls back to the earliest future ISO instant in the error only.
|
|
13
11
|
*/
|
|
14
12
|
export function providerRetryAt(messages, now) {
|
|
15
|
-
const
|
|
13
|
+
const failure = lastAssistantMessage(messages);
|
|
14
|
+
const diagnostic = failure?.providerDiagnostic;
|
|
15
|
+
const reset = diagnostic?.resetsAt;
|
|
16
|
+
if ((diagnostic?.category === "quota" || diagnostic?.category === "rate_limit") &&
|
|
17
|
+
reset !== undefined &&
|
|
18
|
+
Number.isFinite(reset) &&
|
|
19
|
+
reset > now) {
|
|
20
|
+
return reset;
|
|
21
|
+
}
|
|
22
|
+
const text = failure?.errorMessage ?? "";
|
|
23
|
+
const retryAfterMs = parseRetryAfterMsMarker(text);
|
|
24
|
+
if (retryAfterMs !== undefined && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
25
|
+
return now + retryAfterMs;
|
|
26
|
+
}
|
|
27
|
+
const instants = [...text.matchAll(ISO_INSTANT)]
|
|
16
28
|
.map((match) => Date.parse(match[0]))
|
|
17
29
|
.filter((at) => Number.isFinite(at) && at > now);
|
|
18
30
|
return instants.length === 0 ? undefined : Math.min(...instants);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Command watches: a PTY session's output, line-buffered and filtered
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Command watches: a PTY session's output, line-buffered and filtered. A durable (restartable) watch
|
|
3
|
+
* counts its matched lines in a persisted rolling fire window and carries the runaway guard, which
|
|
4
|
+
* pauses it with a visible notice only after a sustained flood; a busy watch is never muted, its
|
|
5
|
+
* wakes are paced by the notifier instead. Output stays in the runtime's bounded history; a record
|
|
6
|
+
* holds at most one unfinished line.
|
|
5
7
|
*/
|
|
6
8
|
import { MonitorLineBuffer } from "./monitor-line-buffer.ts";
|
|
7
9
|
import { type MonitorFireWindow, type MonitorRecordBase, type MonitorSourceHost } from "./monitor-types.ts";
|
|
@@ -17,7 +19,7 @@ export interface RegisterMonitorOptions {
|
|
|
17
19
|
readonly description: string;
|
|
18
20
|
readonly runtime: TerminalRuntimeSession;
|
|
19
21
|
readonly filter?: RegExp;
|
|
20
|
-
/** Durability class from the manifest spec; non-ephemeral records carry the fire
|
|
22
|
+
/** Durability class from the manifest spec; non-ephemeral records carry the fire window and runaway guard. */
|
|
21
23
|
readonly durabilityClass?: MonitorDurabilityClass;
|
|
22
24
|
/** Persisted fire window re-bound by a restore, so a restart cannot reset the budget. */
|
|
23
25
|
readonly fireWindow?: MonitorFireWindow;
|
|
@@ -25,6 +27,14 @@ export interface RegisterMonitorOptions {
|
|
|
25
27
|
readonly expiresAt?: number;
|
|
26
28
|
/** Output lines are a person's words (a chat bridge); goal continuation counts them as new input. */
|
|
27
29
|
readonly humanInput?: boolean;
|
|
30
|
+
/** Per-monitor wake-rate override of a persistent watch; unset uses terminal.monitorWakesPerHour. */
|
|
31
|
+
readonly wakesPerHour?: number;
|
|
32
|
+
}
|
|
33
|
+
/** One-minute bucket of matched lines plus the run of consecutive flood minutes before it. */
|
|
34
|
+
interface RunawayWindow {
|
|
35
|
+
minuteStartMs: number;
|
|
36
|
+
count: number;
|
|
37
|
+
floodMinutes: number;
|
|
28
38
|
}
|
|
29
39
|
export interface MonitorRecord extends MonitorRecordBase {
|
|
30
40
|
readonly id: string;
|
|
@@ -45,6 +55,8 @@ export interface MonitorRecord extends MonitorRecordBase {
|
|
|
45
55
|
paused: boolean;
|
|
46
56
|
settled: boolean;
|
|
47
57
|
fireWindow: MonitorFireWindow | undefined;
|
|
58
|
+
runaway: RunawayWindow | undefined;
|
|
59
|
+
readonly wakesPerHour: number | undefined;
|
|
48
60
|
unsubscribeOutput: (() => void) | undefined;
|
|
49
61
|
unsubscribeExit: (() => void) | undefined;
|
|
50
62
|
readonly humanInput: boolean;
|
|
@@ -60,4 +72,5 @@ export declare class CommandMonitorSource {
|
|
|
60
72
|
adoptFireWindow(monitorId: string, fireWindow: MonitorFireWindow): boolean;
|
|
61
73
|
dispose(): void;
|
|
62
74
|
}
|
|
75
|
+
export {};
|
|
63
76
|
//# sourceMappingURL=command-monitor-source.d.ts.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Command watches: a PTY session's output, line-buffered and filtered
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Command watches: a PTY session's output, line-buffered and filtered. A durable (restartable) watch
|
|
3
|
+
* counts its matched lines in a persisted rolling fire window and carries the runaway guard, which
|
|
4
|
+
* pauses it with a visible notice only after a sustained flood; a busy watch is never muted, its
|
|
5
|
+
* wakes are paced by the notifier instead. Output stays in the runtime's bounded history; a record
|
|
6
|
+
* holds at most one unfinished line.
|
|
5
7
|
*/
|
|
6
8
|
import { MonitorLineBuffer } from "./monitor-line-buffer.js";
|
|
7
9
|
import { allocateMonitorId, } from "./monitor-types.js";
|
|
8
|
-
import {
|
|
10
|
+
import { FIRE_BUDGET_WINDOW_MS, runawayPauseSummary } from "./shared.js";
|
|
9
11
|
import { describeExit } from "./tools/spawn.js";
|
|
10
12
|
export class CommandMonitorSource {
|
|
11
13
|
#records = new Map();
|
|
@@ -43,6 +45,8 @@ export class CommandMonitorSource {
|
|
|
43
45
|
paused: false,
|
|
44
46
|
settled: false,
|
|
45
47
|
fireWindow: durable ? { ...(options.fireWindow ?? { startMs: Date.now(), count: 0 }) } : undefined,
|
|
48
|
+
runaway: undefined,
|
|
49
|
+
wakesPerHour: options.wakesPerHour,
|
|
46
50
|
unsubscribeOutput: undefined,
|
|
47
51
|
unsubscribeExit: undefined,
|
|
48
52
|
humanInput: options.humanInput === true,
|
|
@@ -96,19 +100,42 @@ export class CommandMonitorSource {
|
|
|
96
100
|
if (now - record.fireWindow.startMs >= FIRE_BUDGET_WINDOW_MS)
|
|
97
101
|
record.fireWindow = { startMs: now, count: 0 };
|
|
98
102
|
record.fireWindow.count += 1;
|
|
99
|
-
if (record
|
|
103
|
+
if (this.#floodsRunaway(record, now)) {
|
|
104
|
+
const guard = this.#host.runawayGuard();
|
|
100
105
|
record.paused = true;
|
|
101
106
|
this.#host.emit(record, {
|
|
102
107
|
type: "summary",
|
|
103
108
|
id: record.id,
|
|
104
109
|
description: record.description,
|
|
105
|
-
summary:
|
|
110
|
+
summary: runawayPauseSummary(record.id, guard.linesPerMinute, guard.minutes),
|
|
106
111
|
});
|
|
107
112
|
this.#host.changed();
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Count one matched line into the record's minute bucket. A minute with more lines than the guard
|
|
119
|
+
* allows is a flood minute; the watch trips once the current minute floods and the minutes right
|
|
120
|
+
* before it flooded too, `minutes` in a row. A quiet or skipped minute resets the run.
|
|
121
|
+
*/
|
|
122
|
+
#floodsRunaway(record, now) {
|
|
123
|
+
const guard = this.#host.runawayGuard();
|
|
124
|
+
const previous = record.runaway;
|
|
125
|
+
let current = previous;
|
|
126
|
+
if (current === undefined || now - current.minuteStartMs >= 60_000) {
|
|
127
|
+
const elapsedMinutes = previous === undefined ? 0 : Math.floor((now - previous.minuteStartMs) / 60_000);
|
|
128
|
+
const previousFlooded = previous !== undefined && elapsedMinutes === 1 && previous.count > guard.linesPerMinute;
|
|
129
|
+
current = {
|
|
130
|
+
minuteStartMs: previous === undefined ? now : previous.minuteStartMs + elapsedMinutes * 60_000,
|
|
131
|
+
count: 0,
|
|
132
|
+
floodMinutes: previous !== undefined && previousFlooded ? previous.floodMinutes + 1 : 0,
|
|
133
|
+
};
|
|
134
|
+
record.runaway = current;
|
|
135
|
+
}
|
|
136
|
+
current.count += 1;
|
|
137
|
+
return current.count > guard.linesPerMinute && current.floodMinutes + 1 >= guard.minutes;
|
|
138
|
+
}
|
|
112
139
|
#settle(record) {
|
|
113
140
|
if (record.settled)
|
|
114
141
|
return;
|
|
@@ -129,6 +129,7 @@ export function createRestartableCommandHandler(deps) {
|
|
|
129
129
|
runtime: spawned.runtime,
|
|
130
130
|
filter: compileFilter(monitor.filter),
|
|
131
131
|
...(monitor.humanInput === true ? { humanInput: true } : {}),
|
|
132
|
+
...(monitor.wakesPerHour !== undefined ? { wakesPerHour: monitor.wakesPerHour } : {}),
|
|
132
133
|
// The persisted deadline rides through verbatim; a restore never extends it.
|
|
133
134
|
...(monitor.expiresAt !== null ? { expiresAt: monitor.expiresAt } : {}),
|
|
134
135
|
});
|
|
@@ -6,6 +6,7 @@ import { isEvalOnlyRouting } from "../eval-only-routing.js";
|
|
|
6
6
|
import { buildToolContext, bundleSinks, createBundle, sessionKeyOf, } from "./extension-state.js";
|
|
7
7
|
import { currentLeaseToken, releaseTerminalLease, retireLeaseToken } from "./manifest-lease.js";
|
|
8
8
|
import { MonitorNotifier } from "./monitor-notify.js";
|
|
9
|
+
import { appendMonitorOverflow } from "./monitor-overflow-file.js";
|
|
9
10
|
import { terminalStateDir } from "./monitor-state-dir.js";
|
|
10
11
|
import { MONITOR_STATUS_KEY } from "./monitor-status.js";
|
|
11
12
|
import { MonitorStatusTicker } from "./monitor-status-ticker.js";
|
|
@@ -24,6 +25,26 @@ import { createBashResizeTool } from "./tools/bash-resize.js";
|
|
|
24
25
|
import { TERMINAL_CHILD_JOB_KINDS, terminalChildJob } from "./tools/child-placement.js";
|
|
25
26
|
import { createKillBashTool } from "./tools/kill-bash.js";
|
|
26
27
|
import { bindTerminalManifestWriter, createMonitorTool } from "./tools/monitor.js";
|
|
28
|
+
/**
|
|
29
|
+
* Only paced (persistent command) watches of a session with a durable state dir get an overflow file.
|
|
30
|
+
* A failing file is reported once per monitor, not once per spilled chunk.
|
|
31
|
+
*/
|
|
32
|
+
function spillMonitorOverflow(state, warned, id, lines) {
|
|
33
|
+
const terminalDir = terminalStateDir(state.ctx);
|
|
34
|
+
const monitorId = state.bundle?.manager.monitorIdOf(id);
|
|
35
|
+
if (terminalDir === undefined || monitorId === undefined || state.bundle?.monitors.wakePacing(id) === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
try {
|
|
38
|
+
return appendMonitorOverflow(terminalDir, monitorId, lines);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (warned.has(monitorId))
|
|
42
|
+
return undefined;
|
|
43
|
+
warned.add(monitorId);
|
|
44
|
+
state.ctx?.ui?.notify?.(`Could not write the overflow file of ${monitorId}: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
27
48
|
function shouldStepAside(ctx) {
|
|
28
49
|
return isAnthropicBashEnabled() && ctx?.model?.api === "anthropic-messages";
|
|
29
50
|
}
|
|
@@ -106,12 +127,18 @@ export function registerTerminalExtension(pi) {
|
|
|
106
127
|
getMode: () => state.settings.notify,
|
|
107
128
|
});
|
|
108
129
|
state.monitorNotifier?.dispose();
|
|
130
|
+
const overflowWarned = new Set();
|
|
109
131
|
state.monitorNotifier = new MonitorNotifier({
|
|
110
132
|
sendMessage: (message, options) => pi.sendMessage(message, options),
|
|
111
133
|
getContext: () => state.ctx,
|
|
112
134
|
getMode: () => state.settings.notify,
|
|
113
135
|
getSettings: () => state.settings.monitor,
|
|
114
136
|
pauseMonitors: (ids) => state.bundle?.monitors.pause(ids) ?? [],
|
|
137
|
+
wakesPerHour: (id) => {
|
|
138
|
+
const pacing = state.bundle?.monitors.wakePacing(id);
|
|
139
|
+
return pacing === undefined ? undefined : (pacing.wakesPerHour ?? state.settings.monitor.wakesPerHour);
|
|
140
|
+
},
|
|
141
|
+
spillOverflow: (id, lines) => spillMonitorOverflow(state, overflowWarned, id, lines),
|
|
115
142
|
});
|
|
116
143
|
const sessionKey = sessionKeyOf(ctx);
|
|
117
144
|
if (event.reason === "reload") {
|
|
@@ -128,6 +155,7 @@ export function registerTerminalExtension(pi) {
|
|
|
128
155
|
state.bundle = createBundle(state);
|
|
129
156
|
}
|
|
130
157
|
state.bundle ??= createBundle(state);
|
|
158
|
+
state.bundle.monitors.setRunawayGuard(state.settings.monitorRunaway);
|
|
131
159
|
state.bundle.bind(bundleSinks(pi, state));
|
|
132
160
|
const holdsLease = sessionKey !== undefined && currentLeaseToken(encodedSessionId(sessionKey)) !== undefined;
|
|
133
161
|
if (event.reason === "reload" && holdsLease) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MonitorNotificationDetail } from "../monitor-state-event.ts";
|
|
2
|
+
import type { OverflowBook } from "./monitor-overflow.ts";
|
|
3
|
+
import type { MonitorEvent } from "./monitor-registry.ts";
|
|
4
|
+
/** Per-monitor attribution of one wake: what it carried inline plus what overflowed. */
|
|
5
|
+
export declare function monitorDetails(events: readonly MonitorEvent[], ids: ReadonlySet<string>, overflowBook: OverflowBook): MonitorNotificationDetail[];
|
|
6
|
+
//# sourceMappingURL=monitor-notify-details.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Per-monitor attribution of one wake: what it carried inline plus what overflowed. */
|
|
2
|
+
export function monitorDetails(events, ids, overflowBook) {
|
|
3
|
+
return [...ids].map((id) => {
|
|
4
|
+
const own = events.filter((event) => event.id === id);
|
|
5
|
+
const overflow = overflowBook.get(id);
|
|
6
|
+
const kinds = new Set(own.map((event) => event.type));
|
|
7
|
+
for (const kind of overflow?.kinds ?? [])
|
|
8
|
+
kinds.add(kind);
|
|
9
|
+
const humanInput = own.some((event) => event.type === "line" && event.humanInput === true) || overflow?.humanInput === true;
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
description: own[0]?.description ?? overflow?.description ?? id,
|
|
13
|
+
eventCount: own.length + (overflow?.count ?? 0),
|
|
14
|
+
kinds: [...kinds],
|
|
15
|
+
...(humanInput ? { humanInput: true } : {}),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=monitor-notify-details.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering of monitor wakes: one event body, a monitor's group header, and the size of a
|
|
3
|
+
* monitor's share of a wake, so the notifier can fill a wake by whole monitors without clipping.
|
|
4
|
+
*/
|
|
5
|
+
import type { MonitorEvent } from "./monitor-registry.ts";
|
|
6
|
+
export declare const SYSTEM_REMINDER_OPEN = "<system-reminder>";
|
|
7
|
+
export declare const SYSTEM_REMINDER_CLOSE = "</system-reminder>";
|
|
8
|
+
export declare const WAKE_BUDGET_PAUSE_NOTICE = "Monitor paused after repeated updates. Completion still wakes this session; peek bash_output or re-arm only for intermediate events.";
|
|
9
|
+
export declare function eventBody(event: MonitorEvent): string;
|
|
10
|
+
export declare function renderedLength(event: MonitorEvent): number;
|
|
11
|
+
/** Characters one monitor adds to a wake: its group, its overflow notice, and their separators. */
|
|
12
|
+
export declare function monitorShareChars(events: readonly MonitorEvent[], notice: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* One wake message. The notifier fills a wake by whole monitors within `maxChars`, so the clip is a
|
|
15
|
+
* last-resort guard that a correctly sized wake never reaches.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildWakeMessage(events: readonly MonitorEvent[], notices: readonly string[], pauseNotice: string, maxChars: number): string;
|
|
18
|
+
//# sourceMappingURL=monitor-notify-format.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { sanitizeTerminalOutput } from "./output-format.js";
|
|
2
|
+
export const SYSTEM_REMINDER_OPEN = "<system-reminder>";
|
|
3
|
+
export const SYSTEM_REMINDER_CLOSE = "</system-reminder>";
|
|
4
|
+
export const WAKE_BUDGET_PAUSE_NOTICE = "Monitor paused after repeated updates. Completion still wakes this session; peek bash_output or re-arm only for intermediate events.";
|
|
5
|
+
export function eventBody(event) {
|
|
6
|
+
const value = event.type === "line" ? event.line : event.summary;
|
|
7
|
+
return sanitizeTerminalOutput(value)
|
|
8
|
+
.replace(/[\r\n]+/g, " ")
|
|
9
|
+
.trimEnd();
|
|
10
|
+
}
|
|
11
|
+
export function renderedLength(event) {
|
|
12
|
+
return `Monitor event(${event.description}): ${eventBody(event)}`.length;
|
|
13
|
+
}
|
|
14
|
+
function groupHeader(description) {
|
|
15
|
+
return `Monitor event(${description}): `;
|
|
16
|
+
}
|
|
17
|
+
/** Characters one monitor adds to a wake: its group, its overflow notice, and their separators. */
|
|
18
|
+
export function monitorShareChars(events, notice) {
|
|
19
|
+
const first = events[0];
|
|
20
|
+
const group = first === undefined
|
|
21
|
+
? 0
|
|
22
|
+
: groupHeader(first.description).length +
|
|
23
|
+
events.reduce((total, event) => total + eventBody(event).length + 1, 0);
|
|
24
|
+
return group + (notice.length > 0 ? notice.length + 1 : 0);
|
|
25
|
+
}
|
|
26
|
+
function clip(text, maxChars) {
|
|
27
|
+
if (text.length <= maxChars)
|
|
28
|
+
return text;
|
|
29
|
+
if (maxChars <= 3)
|
|
30
|
+
return text.slice(0, maxChars);
|
|
31
|
+
return `${text.slice(0, maxChars - 3)}...`;
|
|
32
|
+
}
|
|
33
|
+
function formatEvents(events) {
|
|
34
|
+
const groups = new Map();
|
|
35
|
+
for (const event of events) {
|
|
36
|
+
const group = groups.get(event.id) ?? { description: event.description, bodies: [] };
|
|
37
|
+
group.bodies.push(eventBody(event));
|
|
38
|
+
groups.set(event.id, group);
|
|
39
|
+
}
|
|
40
|
+
return [...groups.values()].map((group) => `${groupHeader(group.description)}${group.bodies.join("\n")}`).join("\n");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* One wake message. The notifier fills a wake by whole monitors within `maxChars`, so the clip is a
|
|
44
|
+
* last-resort guard that a correctly sized wake never reaches.
|
|
45
|
+
*/
|
|
46
|
+
export function buildWakeMessage(events, notices, pauseNotice, maxChars) {
|
|
47
|
+
const suffix = [...notices, pauseNotice].filter(Boolean).join("\n");
|
|
48
|
+
const fixedChars = SYSTEM_REMINDER_OPEN.length + SYSTEM_REMINDER_CLOSE.length + (suffix ? suffix.length + 1 : 0);
|
|
49
|
+
const body = clip(formatEvents(events), Math.max(0, maxChars - fixedChars));
|
|
50
|
+
return `${SYSTEM_REMINDER_OPEN}${body}${body && suffix ? "\n" : ""}${suffix}${SYSTEM_REMINDER_CLOSE}`;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=monitor-notify-format.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { type MonitorSpill } from "./monitor-overflow.ts";
|
|
1
2
|
import type { MonitorEvent } from "./monitor-registry.ts";
|
|
2
3
|
import { type TerminalNotifierDeps } from "./notify.ts";
|
|
3
4
|
import type { MonitorDeliverySettings } from "./settings.ts";
|
|
5
|
+
export type { MonitorSpill, MonitorSpillResult } from "./monitor-overflow.ts";
|
|
4
6
|
export interface MonitorNotificationScheduler {
|
|
5
7
|
now(): number;
|
|
6
8
|
setTimeout(callback: () => void, delayMs: number): unknown;
|
|
@@ -10,11 +12,16 @@ export interface MonitorNotifierDeps extends TerminalNotifierDeps {
|
|
|
10
12
|
readonly getSettings: () => MonitorDeliverySettings;
|
|
11
13
|
/** Pause the monitors contributing to a shared wake-budget exhaustion. */
|
|
12
14
|
readonly pauseMonitors: (ids: readonly string[]) => readonly string[];
|
|
15
|
+
/** Wake rate (and burst) of a paced monitor; undefined leaves the monitor unpaced. */
|
|
16
|
+
readonly wakesPerHour?: (id: string) => number | undefined;
|
|
17
|
+
/** Append overflow lines to the monitor's overflow file; undefined when it has none. */
|
|
18
|
+
readonly spillOverflow?: MonitorSpill;
|
|
13
19
|
readonly scheduler?: MonitorNotificationScheduler;
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
16
22
|
* Session-scoped monitor delivery queue. It preserves the terminal runtime as the authoritative,
|
|
17
|
-
* bounded event history while retaining only
|
|
23
|
+
* bounded event history while retaining only capped per-monitor batches for chat injection. A
|
|
24
|
+
* wake is filled by whole monitors; a monitor that does not fit stays queued for the next wake.
|
|
18
25
|
*/
|
|
19
26
|
export declare class MonitorNotifier {
|
|
20
27
|
#private;
|