@code-yeongyu/senpi-agent-core 2026.10.27-2 → 2026.10.30-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/dist/agent-loop.js +26 -7
- package/dist/agent.d.ts +3 -0
- package/dist/agent.js +2 -0
- package/dist/conformance/index.d.ts +42 -0
- package/dist/conformance/index.js +23 -0
- package/dist/conformance/mapping.d.ts +24 -0
- package/dist/conformance/mapping.js +62 -0
- package/dist/conformance/registry.d.ts +15 -0
- package/dist/conformance/registry.js +75 -0
- package/dist/conformance/runner.d.ts +27 -0
- package/dist/conformance/runner.js +23 -0
- package/dist/conformance/suite-hash.d.ts +12 -0
- package/dist/conformance/suite-hash.js +20 -0
- package/dist/durable/store-admission.d.ts +7 -0
- package/dist/durable/store-admission.js +11 -0
- package/dist/durable/store-memory.d.ts +15 -0
- package/dist/durable/store-memory.js +140 -0
- package/dist/durable/store.d.ts +116 -0
- package/dist/durable/store.js +14 -0
- package/dist/durable/testing/kill-store.d.ts +13 -0
- package/dist/durable/testing/kill-store.js +77 -0
- package/dist/durable/testing/kill-wake.d.ts +12 -0
- package/dist/durable/testing/kill-wake.js +123 -0
- package/dist/durable/testing/manual-timers.d.ts +8 -0
- package/dist/durable/testing/manual-timers.js +31 -0
- package/dist/durable/testing/multi-host-cases.d.ts +6 -0
- package/dist/durable/testing/multi-host-cases.js +32 -0
- package/dist/durable/testing/store-cases.d.ts +21 -0
- package/dist/durable/testing/store-cases.js +168 -0
- package/dist/durable/testing/store-harness.d.ts +23 -0
- package/dist/durable/testing/store-harness.js +64 -0
- package/dist/durable/testing/wake-cases.d.ts +6 -0
- package/dist/durable/testing/wake-cases.js +100 -0
- package/dist/durable/testing/wake-harness.d.ts +64 -0
- package/dist/durable/testing/wake-harness.js +127 -0
- package/dist/durable/testing/wake-test-loop.d.ts +13 -0
- package/dist/durable/testing/wake-test-loop.js +40 -0
- package/dist/durable/wake-host-bun-timer.d.ts +31 -0
- package/dist/durable/wake-host-bun-timer.js +66 -0
- package/dist/durable/wake-host-store-alarm.d.ts +13 -0
- package/dist/durable/wake-host-store-alarm.js +20 -0
- package/dist/durable/wake-host.d.ts +68 -0
- package/dist/durable/wake-host.js +70 -0
- package/dist/harness/utils/read-folders/index.d.ts +1 -1
- package/dist/harness/utils/read-folders/index.js +1 -1
- package/dist/harness/utils/read-folders/tree-sitter/engine.d.ts +1 -1
- package/dist/harness/utils/read-folders/tree-sitter/engine.js +38 -19
- package/dist/stream-keepalive.d.ts +28 -0
- package/dist/stream-keepalive.js +42 -0
- package/dist/testing/win32-sim.d.ts +76 -0
- package/dist/testing/win32-sim.js +197 -0
- package/dist/transport/in-process-carrier.d.ts +19 -0
- package/dist/transport/in-process-carrier.js +63 -0
- package/dist/transport/local-endpoint.d.ts +13 -0
- package/dist/transport/local-endpoint.js +20 -0
- package/dist/transport/reliable-transport.d.ts +40 -0
- package/dist/transport/reliable-transport.js +232 -0
- package/dist/transport/testing/fault-cases.d.ts +6 -0
- package/dist/transport/testing/fault-cases.js +30 -0
- package/dist/transport/testing/fault-transport.d.ts +44 -0
- package/dist/transport/testing/fault-transport.js +144 -0
- package/dist/transport/testing/multi-cases.d.ts +6 -0
- package/dist/transport/testing/multi-cases.js +113 -0
- package/dist/transport/testing/transport-cases.d.ts +6 -0
- package/dist/transport/testing/transport-cases.js +60 -0
- package/dist/transport/testing/transport-harness.d.ts +53 -0
- package/dist/transport/testing/transport-harness.js +127 -0
- package/dist/transport/transport.d.ts +60 -0
- package/dist/transport/transport.js +15 -0
- package/dist/types.d.ts +6 -0
- package/package.json +11 -3
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const DEFAULT_MAX_DELAY_MS = 60_000;
|
|
2
|
+
const defaultSetTimer = (callback, ms) => {
|
|
3
|
+
const handle = setTimeout(callback, ms);
|
|
4
|
+
return () => clearTimeout(handle);
|
|
5
|
+
};
|
|
6
|
+
export function createBunTimerHost(options) {
|
|
7
|
+
const now = options.now ?? Date.now;
|
|
8
|
+
const setTimer = options.setTimer ?? defaultSetTimer;
|
|
9
|
+
const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
10
|
+
const entries = new Map();
|
|
11
|
+
const delivering = new Set();
|
|
12
|
+
function schedule(key, entry, retry = false) {
|
|
13
|
+
entry.cancel?.();
|
|
14
|
+
entry.cancel = undefined;
|
|
15
|
+
if (entry.at === undefined || entries.get(key) !== entry || delivering.has(key))
|
|
16
|
+
return;
|
|
17
|
+
const delay = retry ? maxDelayMs : Math.min(Math.max(0, entry.at - now()), maxDelayMs);
|
|
18
|
+
entry.cancel = setTimer(() => tick(key, entry), delay);
|
|
19
|
+
}
|
|
20
|
+
async function tick(key, entry) {
|
|
21
|
+
entry.cancel = undefined;
|
|
22
|
+
const firedAt = entry.at;
|
|
23
|
+
if (firedAt === undefined || entries.get(key) !== entry)
|
|
24
|
+
return;
|
|
25
|
+
if (now() < firedAt)
|
|
26
|
+
return schedule(key, entry);
|
|
27
|
+
delivering.add(key);
|
|
28
|
+
try {
|
|
29
|
+
await entry.onFire();
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
options.onFireError(key, error);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
delivering.delete(key);
|
|
36
|
+
const current = entries.get(key);
|
|
37
|
+
if (current !== undefined)
|
|
38
|
+
schedule(key, current, current === entry && current.at === firedAt);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
async openAlarm(key, persistence, onFire, alarmOptions = {}) {
|
|
43
|
+
const entry = { at: await persistence.read(), cancel: undefined, onFire };
|
|
44
|
+
entries.get(key)?.cancel?.();
|
|
45
|
+
entries.set(key, entry);
|
|
46
|
+
schedule(key, entry);
|
|
47
|
+
const write = async (at) => {
|
|
48
|
+
if (entries.get(key) !== entry)
|
|
49
|
+
throw new Error(`the alarm instance of ${key} was replaced by a newer one`);
|
|
50
|
+
const point = (stage) => alarmOptions.onAlarmPoint?.(at === undefined ? { op: "clear", at: stage } : { op: "set", at: stage, time: at });
|
|
51
|
+
point("before");
|
|
52
|
+
await persistence.write(at);
|
|
53
|
+
entry.at = at;
|
|
54
|
+
schedule(key, entry);
|
|
55
|
+
point("durable");
|
|
56
|
+
};
|
|
57
|
+
return { read: () => persistence.read(), set: (at) => write(at), clear: () => write(undefined) };
|
|
58
|
+
},
|
|
59
|
+
close() {
|
|
60
|
+
for (const entry of entries.values())
|
|
61
|
+
entry.cancel?.();
|
|
62
|
+
entries.clear();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=wake-host-bun-timer.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DurableStore } from "./store.ts";
|
|
2
|
+
/** Where a timer host keeps one session's armed alarm, durably. */
|
|
3
|
+
export interface AlarmPersistence {
|
|
4
|
+
read(): Promise<number | undefined>;
|
|
5
|
+
/** Durably arm at `at`, or clear with `undefined`. */
|
|
6
|
+
write(at: number | undefined): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The alarm as a document in the session's own DurableStore, written under the owner `epoch`: an old owner's alarm
|
|
10
|
+
* write is refused (`stale-epoch`) like any other write of it, and the alarm moves with the store on claim + import.
|
|
11
|
+
*/
|
|
12
|
+
export declare function storeAlarmPersistence(store: DurableStore, epoch: number): AlarmPersistence;
|
|
13
|
+
//# sourceMappingURL=wake-host-store-alarm.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const WAKE_ALARM_DOCUMENT = "durable.wake.alarm";
|
|
2
|
+
/**
|
|
3
|
+
* The alarm as a document in the session's own DurableStore, written under the owner `epoch`: an old owner's alarm
|
|
4
|
+
* write is refused (`stale-epoch`) like any other write of it, and the alarm moves with the store on claim + import.
|
|
5
|
+
*/
|
|
6
|
+
export function storeAlarmPersistence(store, epoch) {
|
|
7
|
+
return {
|
|
8
|
+
read: async () => {
|
|
9
|
+
const at = await store.getDocument(WAKE_ALARM_DOCUMENT);
|
|
10
|
+
return typeof at === "number" ? at : undefined;
|
|
11
|
+
},
|
|
12
|
+
write: async (at) => {
|
|
13
|
+
const write = at === undefined
|
|
14
|
+
? { op: "delete", key: WAKE_ALARM_DOCUMENT }
|
|
15
|
+
: { op: "put", key: WAKE_ALARM_DOCUMENT, data: at };
|
|
16
|
+
await store.commit(epoch, [write], () => { });
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=wake-host-store-alarm.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WakeHost contract of the durable core (packages/agent/docs/durable-core-adr.md): wake ordering for hosts that can
|
|
3
|
+
* be evicted (a Durable Object) or put to sleep (a laptop). A process timer is lost with its host and runs late across
|
|
4
|
+
* a sleep, so the host keeps a durable alarm and follows one ordering, shared by every host here:
|
|
5
|
+
*
|
|
6
|
+
* - admit: arm a recovery heartbeat durably BEFORE work that can commit starts. A commit whose host dies before the
|
|
7
|
+
* alarm update would otherwise strand its wake; an early alarm is harmless, a missing one loses work. The host counts
|
|
8
|
+
* admissions in flight and arms as active while any is, so no concurrent update or fire can clear that heartbeat
|
|
9
|
+
* before the admitted work commits; when the last admitted work succeeds, it arms from the loop's wake.
|
|
10
|
+
* - update: after each commit, arm min(heartbeat if active, the loop's earliest wake); clear only when idle.
|
|
11
|
+
* - fire: re-arm the heartbeat before resuming, so a resume that dies is retried; then arm from the loop's wake.
|
|
12
|
+
* - start (a new instance): arm a fallback before anything can throw, then resume what a lost instance left.
|
|
13
|
+
*
|
|
14
|
+
* Timers inside the loop are not the WakeHost's. Where a host can neither be evicted nor sleep it reduces to a timer.
|
|
15
|
+
*/
|
|
16
|
+
/** The loop's wake state: `active` while some work can run now or is running; `at`, the earliest later wake. */
|
|
17
|
+
export interface Wake {
|
|
18
|
+
readonly active: boolean;
|
|
19
|
+
readonly at?: number;
|
|
20
|
+
}
|
|
21
|
+
/** A host's durable alarm for one session; the armed time (epoch ms) survives instance loss and host restarts. */
|
|
22
|
+
export interface WakeAlarm {
|
|
23
|
+
read(): Promise<number | undefined>;
|
|
24
|
+
set(at: number): Promise<void>;
|
|
25
|
+
clear(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** A point inside an alarm write. `before`: nothing is durable yet. `durable`: the write is durable. */
|
|
28
|
+
export type WakeAlarmPoint = {
|
|
29
|
+
readonly op: "set";
|
|
30
|
+
readonly at: "before" | "durable";
|
|
31
|
+
readonly time: number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly op: "clear";
|
|
34
|
+
readonly at: "before" | "durable";
|
|
35
|
+
readonly time?: undefined;
|
|
36
|
+
};
|
|
37
|
+
export interface WakeAlarmOptions {
|
|
38
|
+
/** Test seam for cuts and kills: a throw at `before` leaves the alarm unchanged, at `durable` leaves it written. */
|
|
39
|
+
readonly onAlarmPoint?: (point: WakeAlarmPoint) => void;
|
|
40
|
+
}
|
|
41
|
+
/** The point after an operation's arm is durable and before its work (`admit`) or resume (`start`, `fire`) runs. */
|
|
42
|
+
export interface WakePoint {
|
|
43
|
+
readonly op: "start" | "admit" | "fire";
|
|
44
|
+
readonly at: "armed";
|
|
45
|
+
}
|
|
46
|
+
export interface WakeHostOptions {
|
|
47
|
+
readonly alarm: WakeAlarm;
|
|
48
|
+
/** The host clock, epoch milliseconds; the same clock the loop's wake times use. */
|
|
49
|
+
readonly now: () => number;
|
|
50
|
+
readonly wake: () => Wake;
|
|
51
|
+
/** Run what the loop can run now: due wakes and work a lost instance left. Resolves when it has. */
|
|
52
|
+
readonly resume: () => Promise<void>;
|
|
53
|
+
/** The recovery interval while work is active or admitted. Default 60 s. */
|
|
54
|
+
readonly heartbeatMs?: number;
|
|
55
|
+
/** Test seam for kills between an arm and the work it protects. */
|
|
56
|
+
readonly onWakePoint?: (point: WakePoint) => void;
|
|
57
|
+
}
|
|
58
|
+
export interface WakeHost {
|
|
59
|
+
/** A new instance: arm a fallback, resume, then arm from the loop's wake. */
|
|
60
|
+
start(): Promise<void>;
|
|
61
|
+
/** Run work that can commit, after a recovery heartbeat is durable. */
|
|
62
|
+
admit<T>(work: () => Promise<T>): Promise<T>;
|
|
63
|
+
update(wake: Wake): Promise<void>;
|
|
64
|
+
/** The alarm fired: re-arm the heartbeat, resume, then arm from the loop's wake. */
|
|
65
|
+
fire(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export declare function createWakeHost(options: WakeHostOptions): WakeHost;
|
|
68
|
+
//# sourceMappingURL=wake-host.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WakeHost contract of the durable core (packages/agent/docs/durable-core-adr.md): wake ordering for hosts that can
|
|
3
|
+
* be evicted (a Durable Object) or put to sleep (a laptop). A process timer is lost with its host and runs late across
|
|
4
|
+
* a sleep, so the host keeps a durable alarm and follows one ordering, shared by every host here:
|
|
5
|
+
*
|
|
6
|
+
* - admit: arm a recovery heartbeat durably BEFORE work that can commit starts. A commit whose host dies before the
|
|
7
|
+
* alarm update would otherwise strand its wake; an early alarm is harmless, a missing one loses work. The host counts
|
|
8
|
+
* admissions in flight and arms as active while any is, so no concurrent update or fire can clear that heartbeat
|
|
9
|
+
* before the admitted work commits; when the last admitted work succeeds, it arms from the loop's wake.
|
|
10
|
+
* - update: after each commit, arm min(heartbeat if active, the loop's earliest wake); clear only when idle.
|
|
11
|
+
* - fire: re-arm the heartbeat before resuming, so a resume that dies is retried; then arm from the loop's wake.
|
|
12
|
+
* - start (a new instance): arm a fallback before anything can throw, then resume what a lost instance left.
|
|
13
|
+
*
|
|
14
|
+
* Timers inside the loop are not the WakeHost's. Where a host can neither be evicted nor sleep it reduces to a timer.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_HEARTBEAT_MS = 60_000;
|
|
17
|
+
/** The alarm time for a wake state: a heartbeat while active, never later than the earliest wake. */
|
|
18
|
+
function recoveryAlarmAt(wake, now, heartbeatMs) {
|
|
19
|
+
const heartbeat = wake.active ? now + heartbeatMs : undefined;
|
|
20
|
+
if (heartbeat === undefined)
|
|
21
|
+
return wake.at;
|
|
22
|
+
return wake.at === undefined ? heartbeat : Math.min(heartbeat, wake.at);
|
|
23
|
+
}
|
|
24
|
+
export function createWakeHost(options) {
|
|
25
|
+
const { alarm, now, wake, resume } = options;
|
|
26
|
+
const heartbeatMs = options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
|
|
27
|
+
const point = (op) => options.onWakePoint?.({ op, at: "armed" });
|
|
28
|
+
let admitted = 0;
|
|
29
|
+
const arm = async (state) => {
|
|
30
|
+
const at = recoveryAlarmAt(admitted > 0 ? { ...state, active: true } : state, now(), heartbeatMs);
|
|
31
|
+
if (at === undefined)
|
|
32
|
+
await alarm.clear();
|
|
33
|
+
else
|
|
34
|
+
await alarm.set(at);
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
async start() {
|
|
38
|
+
const fallback = now() + heartbeatMs;
|
|
39
|
+
const current = await alarm.read();
|
|
40
|
+
if (current === undefined || current > fallback)
|
|
41
|
+
await alarm.set(fallback);
|
|
42
|
+
point("start");
|
|
43
|
+
await resume();
|
|
44
|
+
await arm(wake());
|
|
45
|
+
},
|
|
46
|
+
async admit(work) {
|
|
47
|
+
admitted++;
|
|
48
|
+
let result;
|
|
49
|
+
try {
|
|
50
|
+
await arm({ ...wake(), active: true });
|
|
51
|
+
point("admit");
|
|
52
|
+
result = await work();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
admitted--;
|
|
56
|
+
}
|
|
57
|
+
if (admitted === 0)
|
|
58
|
+
await arm(wake());
|
|
59
|
+
return result;
|
|
60
|
+
},
|
|
61
|
+
update: (state) => arm(state),
|
|
62
|
+
async fire() {
|
|
63
|
+
await arm({ ...wake(), active: true });
|
|
64
|
+
point("fire");
|
|
65
|
+
await resume();
|
|
66
|
+
await arm(wake());
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=wake-host.js.map
|
|
@@ -3,7 +3,7 @@ export * from "./types.ts";
|
|
|
3
3
|
/** Frozen measured selection, not a runtime registry that can enable unmeasured grammars. */
|
|
4
4
|
export declare const READ_FOLDER_SELECTION: Readonly<{
|
|
5
5
|
readonly head: "6da8ef1361ef73928df8685e071f2a1826d3b17e";
|
|
6
|
-
readonly selectionSha256: "
|
|
6
|
+
readonly selectionSha256: "dd4b1fa66ed151e330cd7efaf7ca649d244257d52e2a79422e868da60c0edd8b";
|
|
7
7
|
readonly wasm: true;
|
|
8
8
|
readonly rawReasons: Readonly<{
|
|
9
9
|
readonly ts: "wasm_candidate_below_threshold";
|
|
@@ -4,7 +4,7 @@ export * from "./types.js";
|
|
|
4
4
|
/** Frozen measured selection, not a runtime registry that can enable unmeasured grammars. */
|
|
5
5
|
export const READ_FOLDER_SELECTION = Object.freeze({
|
|
6
6
|
head: "6da8ef1361ef73928df8685e071f2a1826d3b17e",
|
|
7
|
-
selectionSha256: "
|
|
7
|
+
selectionSha256: "dd4b1fa66ed151e330cd7efaf7ca649d244257d52e2a79422e868da60c0edd8b",
|
|
8
8
|
wasm: true,
|
|
9
9
|
rawReasons: Object.freeze({
|
|
10
10
|
ts: "wasm_candidate_below_threshold",
|
|
@@ -11,7 +11,7 @@ export type TreeSitterFolderOptions = {
|
|
|
11
11
|
readonly parseBudgetMs?: number;
|
|
12
12
|
readonly resolveGrammar?: GrammarResolver;
|
|
13
13
|
readonly resolveRuntime?: () => Promise<string | undefined>;
|
|
14
|
-
/**
|
|
14
|
+
/** Controls grammar/parser sharing; runtime initialization is always shared by resolved path. */
|
|
15
15
|
readonly cache?: boolean;
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
@@ -5,32 +5,47 @@ import { foldRangesFromSyntax } from "./syntax.js";
|
|
|
5
5
|
export const TREE_SITTER_PARSE_BUDGET_MS = 250;
|
|
6
6
|
export const TREE_SITTER_FOLDER_ID = "tree-sitter-wasm";
|
|
7
7
|
export const TREE_SITTER_FOLDER_VERSION = "1";
|
|
8
|
-
|
|
8
|
+
const runtimes = new Map();
|
|
9
9
|
const grammars = new Map();
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Parser.init and Language.load share process-global state, even across cache resets.
|
|
11
|
+
let runtimeLoads = Promise.resolve();
|
|
12
|
+
function loadRuntime(wasm) {
|
|
13
|
+
const existing = runtimes.get(wasm);
|
|
14
|
+
if (existing)
|
|
15
|
+
return existing;
|
|
16
|
+
const pending = runtimeLoads
|
|
17
|
+
.then(async () => {
|
|
12
18
|
const module = await import("web-tree-sitter");
|
|
13
|
-
const wasm = await resolveRuntime();
|
|
14
19
|
await module.Parser.init(wasm ? { locateFile: () => wasm } : undefined);
|
|
15
20
|
return module;
|
|
16
|
-
}
|
|
17
|
-
|
|
21
|
+
})
|
|
22
|
+
.catch(() => {
|
|
23
|
+
if (runtimes.get(wasm) === pending)
|
|
24
|
+
runtimes.delete(wasm);
|
|
18
25
|
return undefined;
|
|
19
|
-
}
|
|
26
|
+
});
|
|
27
|
+
runtimes.set(wasm, pending);
|
|
28
|
+
return pending;
|
|
20
29
|
}
|
|
21
30
|
async function loadParser(language, options) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
try {
|
|
32
|
+
const grammar = await (options.resolveGrammar ?? resolveGrammarPath)(language);
|
|
33
|
+
// A missing grammar is a stable negative cache entry, not a failed load.
|
|
34
|
+
if (!grammar)
|
|
35
|
+
return null;
|
|
36
|
+
const wasm = await (options.resolveRuntime ?? resolveRuntimePath)();
|
|
37
|
+
const runtime = loadRuntime(wasm);
|
|
38
|
+
const pending = runtimeLoads.then(async () => {
|
|
39
|
+
const module = await runtime;
|
|
40
|
+
return module && instantiate(module, grammar, language);
|
|
41
|
+
});
|
|
42
|
+
// Reserve the whole init/grammar operation before another path can enqueue an init.
|
|
43
|
+
runtimeLoads = pending.then(() => undefined);
|
|
44
|
+
return pending;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
26
47
|
return undefined;
|
|
27
|
-
if (options.cache === false) {
|
|
28
|
-
const module = await loadRuntime(resolveRuntime);
|
|
29
|
-
return module && instantiate(module, grammar, language);
|
|
30
48
|
}
|
|
31
|
-
runtime ??= loadRuntime(resolveRuntime);
|
|
32
|
-
const module = await runtime;
|
|
33
|
-
return module && instantiate(module, grammar, language);
|
|
34
49
|
}
|
|
35
50
|
async function instantiate(module, grammar, language) {
|
|
36
51
|
try {
|
|
@@ -76,7 +91,11 @@ export async function loadTreeSitterFolder(language, options) {
|
|
|
76
91
|
? load()
|
|
77
92
|
: (grammars.get(language) ??
|
|
78
93
|
(() => {
|
|
79
|
-
const pending = load()
|
|
94
|
+
const pending = load().then((loaded) => {
|
|
95
|
+
if (loaded === undefined && grammars.get(language) === pending)
|
|
96
|
+
grammars.delete(language);
|
|
97
|
+
return loaded;
|
|
98
|
+
});
|
|
80
99
|
grammars.set(language, pending);
|
|
81
100
|
return pending;
|
|
82
101
|
})()));
|
|
@@ -91,7 +110,7 @@ export async function loadTreeSitterFolder(language, options) {
|
|
|
91
110
|
}
|
|
92
111
|
/** Test seam: drop the memoized runtime/grammars so a resolver change is observable. */
|
|
93
112
|
export function resetTreeSitterGrammars() {
|
|
94
|
-
|
|
113
|
+
runtimes.clear();
|
|
95
114
|
grammars.clear();
|
|
96
115
|
}
|
|
97
116
|
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport liveness for one provider request (senpi#4608).
|
|
3
|
+
*
|
|
4
|
+
* The loop's silence bounds count public assistant events. A provider that accepted the request and keeps its
|
|
5
|
+
* connection alive - SSE `ping` events, `:` comment keep-alives a relay sends while the upstream model is still
|
|
6
|
+
* thinking - emits none of those, so a slow first token used to be killed as if the connection were dead. Providers
|
|
7
|
+
* report received bytes through `onTransportActivity`; a silence bound that fires within one bound of the last
|
|
8
|
+
* byte is extended instead of ending the request, and keep-alives alone are bounded by their own budget.
|
|
9
|
+
*/
|
|
10
|
+
export declare class StreamKeepAliveTimeoutError extends Error {
|
|
11
|
+
constructor(timeoutMs: number);
|
|
12
|
+
}
|
|
13
|
+
export type SilenceDeadlineVerdict = {
|
|
14
|
+
kind: "expire";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "extend";
|
|
17
|
+
delayMs: number;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "keep-alive-exhausted";
|
|
20
|
+
error: StreamKeepAliveTimeoutError;
|
|
21
|
+
};
|
|
22
|
+
export interface TransportLiveness {
|
|
23
|
+
readonly noteActivity: () => void;
|
|
24
|
+
contentArrived(): void;
|
|
25
|
+
onSilenceDeadline(windowMs: number): SilenceDeadlineVerdict;
|
|
26
|
+
}
|
|
27
|
+
export declare function createTransportLiveness(keepAliveTimeoutMs: number | undefined): TransportLiveness;
|
|
28
|
+
//# sourceMappingURL=stream-keepalive.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport liveness for one provider request (senpi#4608).
|
|
3
|
+
*
|
|
4
|
+
* The loop's silence bounds count public assistant events. A provider that accepted the request and keeps its
|
|
5
|
+
* connection alive - SSE `ping` events, `:` comment keep-alives a relay sends while the upstream model is still
|
|
6
|
+
* thinking - emits none of those, so a slow first token used to be killed as if the connection were dead. Providers
|
|
7
|
+
* report received bytes through `onTransportActivity`; a silence bound that fires within one bound of the last
|
|
8
|
+
* byte is extended instead of ending the request, and keep-alives alone are bounded by their own budget.
|
|
9
|
+
*/
|
|
10
|
+
// The wording keeps "timed out" so the retryable-error classifier treats it like the other stream bounds.
|
|
11
|
+
export class StreamKeepAliveTimeoutError extends Error {
|
|
12
|
+
constructor(timeoutMs) {
|
|
13
|
+
super(`Provider stream sent only keep-alives and timed out after ${timeoutMs}ms (raise streamKeepAliveTimeoutMs: retry.provider.keepAliveTimeoutMs in senpi settings; 0 disables)`);
|
|
14
|
+
this.name = "StreamKeepAliveTimeoutError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createTransportLiveness(keepAliveTimeoutMs) {
|
|
18
|
+
let lastActivityAt;
|
|
19
|
+
let contentSilenceSince = Date.now();
|
|
20
|
+
return {
|
|
21
|
+
noteActivity: () => {
|
|
22
|
+
lastActivityAt = Date.now();
|
|
23
|
+
},
|
|
24
|
+
contentArrived: () => {
|
|
25
|
+
contentSilenceSince = Date.now();
|
|
26
|
+
},
|
|
27
|
+
onSilenceDeadline: (windowMs) => {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
if (lastActivityAt === undefined || now - lastActivityAt >= windowMs)
|
|
30
|
+
return { kind: "expire" };
|
|
31
|
+
const delayMs = windowMs - (now - lastActivityAt);
|
|
32
|
+
if (keepAliveTimeoutMs === undefined)
|
|
33
|
+
return { kind: "extend", delayMs };
|
|
34
|
+
const keepAliveLeftMs = keepAliveTimeoutMs - (now - contentSilenceSince);
|
|
35
|
+
if (keepAliveLeftMs <= 0) {
|
|
36
|
+
return { kind: "keep-alive-exhausted", error: new StreamKeepAliveTimeoutError(keepAliveTimeoutMs) };
|
|
37
|
+
}
|
|
38
|
+
return { kind: "extend", delayMs: Math.max(1, Math.min(delayMs, keepAliveLeftMs)) };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=stream-keepalive.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Win32 simulation profile for code-level Windows tests on any host (test support only; no product code imports it).
|
|
3
|
+
*
|
|
4
|
+
* It simulates five Windows behaviors:
|
|
5
|
+
* - `\` paths with a drive letter (`C:\dir\file`), mapped onto a backing POSIX directory;
|
|
6
|
+
* - case-insensitive, case-preserving names;
|
|
7
|
+
* - named-pipe endpoints (`\\.\pipe\<name>`), which are not filesystem paths;
|
|
8
|
+
* - `EBUSY`/`EPERM` on rename or delete of a file that is open;
|
|
9
|
+
* - signal-less process termination: a supported signal terminates at once and no handler runs.
|
|
10
|
+
*/
|
|
11
|
+
import type { ChildProcess } from "node:child_process";
|
|
12
|
+
import * as fsp from "node:fs/promises";
|
|
13
|
+
type Win32ErrorCode = "EBUSY" | "EPERM" | "ENOSYS";
|
|
14
|
+
export type Win32OpenFileErrorCode = "EBUSY" | "EPERM";
|
|
15
|
+
/** The errno error shape Node throws on Windows, with `dest` for two-path calls such as rename. */
|
|
16
|
+
export interface Win32ErrnoException extends NodeJS.ErrnoException {
|
|
17
|
+
dest?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Builds the error Node throws on Windows for `code` from `syscall`. */
|
|
20
|
+
export declare function win32Error(code: Win32ErrorCode, syscall: string, filePath?: string, dest?: string): Win32ErrnoException;
|
|
21
|
+
/** Compares two Windows names (path segments, paths or pipe names) the way Windows does: case-insensitively. */
|
|
22
|
+
export declare function win32NamesEqual(left: string, right: string): boolean;
|
|
23
|
+
/** Turns a relative POSIX-style path (`dir/file`) into a drive-letter Windows path (`C:\dir\file`). */
|
|
24
|
+
export declare function toWin32Path(relativePath: string, drive?: string): string;
|
|
25
|
+
/** True for an absolute drive-letter path such as `C:\dir` (forward slashes are accepted, as on Windows). */
|
|
26
|
+
export declare function isWin32DrivePath(candidate: string): boolean;
|
|
27
|
+
export declare const WIN32_PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
28
|
+
/** Builds the named-pipe endpoint (`\\.\pipe\<name>`) a Windows transport listens on instead of a socket file. */
|
|
29
|
+
export declare function win32PipeEndpoint(name: string): string;
|
|
30
|
+
/** True when `endpoint` is a local named-pipe endpoint; the prefix is matched case-insensitively, as on Windows. */
|
|
31
|
+
export declare function isWin32PipeEndpoint(endpoint: string): boolean;
|
|
32
|
+
export interface Win32SimFsOptions {
|
|
33
|
+
/** Backing POSIX directory; drive `C:` maps to `<root>/C`. */
|
|
34
|
+
root: string;
|
|
35
|
+
/** Error for a rename whose source or destination is open. Default `EPERM`. */
|
|
36
|
+
renameOpenFileError?: Win32OpenFileErrorCode;
|
|
37
|
+
/** Error for an unlink or rm that would remove an open file. Default `EBUSY`. */
|
|
38
|
+
deleteOpenFileError?: Win32OpenFileErrorCode;
|
|
39
|
+
}
|
|
40
|
+
/** An open file in a {@link Win32SimFs}; the file counts as open until `close()`. */
|
|
41
|
+
export interface Win32SimFileHandle {
|
|
42
|
+
readonly handle: fsp.FileHandle;
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A filesystem that behaves like Windows over a backing directory: it takes only drive-letter paths, resolves every
|
|
47
|
+
* segment case-insensitively while keeping the case a name was created with, and refuses to rename or delete an open
|
|
48
|
+
* file with Windows errors.
|
|
49
|
+
*/
|
|
50
|
+
export declare class Win32SimFs {
|
|
51
|
+
#private;
|
|
52
|
+
constructor(options: Win32SimFsOptions);
|
|
53
|
+
/** Maps a drive-letter path to its backing path, matching existing names case-insensitively. */
|
|
54
|
+
resolve(win32Path: string): Promise<string>;
|
|
55
|
+
mkdir(win32Path: string): Promise<void>;
|
|
56
|
+
writeFile(win32Path: string, data: string | Uint8Array): Promise<void>;
|
|
57
|
+
readFile(win32Path: string): Promise<Buffer>;
|
|
58
|
+
/** Names in a directory, in the case each was created with. */
|
|
59
|
+
readdir(win32Path: string): Promise<string[]>;
|
|
60
|
+
exists(win32Path: string): Promise<boolean>;
|
|
61
|
+
/** Opens a file; until `close()` it cannot be renamed or deleted. */
|
|
62
|
+
open(win32Path: string, flags?: string): Promise<Win32SimFileHandle>;
|
|
63
|
+
/** Renames or replaces a file; a rename that only changes a name's case keeps one entry with the new case. */
|
|
64
|
+
rename(from: string, to: string): Promise<void>;
|
|
65
|
+
unlink(win32Path: string): Promise<void>;
|
|
66
|
+
/** Like `fs.rm` with `recursive`; fails with the delete error while any file at or under the path is open. */
|
|
67
|
+
rm(win32Path: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Sends `signal` with Windows semantics. Signal `0` only probes liveness. A supported signal terminates the process
|
|
71
|
+
* immediately, so no signal handler runs (the POSIX child is sent `SIGKILL` and reports it). Any other signal throws
|
|
72
|
+
* `ENOSYS` and leaves the process running.
|
|
73
|
+
*/
|
|
74
|
+
export declare function win32Kill(target: ChildProcess | number, signal?: NodeJS.Signals | 0): boolean;
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=win32-sim.d.ts.map
|