@sema-agent/core 2.13.0 → 4.0.0
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 +2345 -0
- package/dist/agents/cascade.d.ts +6 -1
- package/dist/agents/cascade.js +76 -32
- package/dist/agents/cumulative-stats.d.ts +29 -0
- package/dist/agents/cumulative-stats.js +29 -0
- package/dist/agents/observer.d.ts +4 -0
- package/dist/agents/observer.js +11 -10
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +110 -3
- package/dist/agents/retain-ledger.d.ts +20 -2
- package/dist/agents/retain-ledger.js +38 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +20 -1
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +23 -24
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +39 -3
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/anthropic.js +24 -21
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/openai.js +24 -21
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +5 -2
- package/dist/brain/stream-shared.d.ts +1 -0
- package/dist/brain/stream-shared.js +14 -0
- package/dist/brain/terminal-cause.d.ts +4 -0
- package/dist/brain/terminal-cause.js +12 -0
- package/dist/brain/tool-call-id.d.ts +1 -0
- package/dist/brain/tool-call-id.js +3 -0
- package/dist/brain/tool-call-repair.js +34 -15
- package/dist/core/auto-compaction.js +33 -32
- package/dist/core/background-agent-store.d.ts +22 -22
- package/dist/core/background-agent-store.js +19 -23
- package/dist/core/checkpoint-store.d.ts +7 -13
- package/dist/core/checkpoint-store.js +39 -15
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +12 -1
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +35 -7
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +65 -9
- package/dist/core/mailbox-store.d.ts +7 -10
- package/dist/core/mcp.d.ts +7 -10
- package/dist/core/mcp.js +7 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +96 -31
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +38 -39
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +71 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +1 -15
- package/dist/core/runner/assemble-result.d.ts +3 -8
- package/dist/core/runner/assemble-result.js +3 -5
- package/dist/core/runner/compaction-call-options.d.ts +7 -0
- package/dist/core/runner/compaction-call-options.js +30 -0
- package/dist/core/runner/prepare-task.d.ts +6 -23
- package/dist/core/runner/prepare-task.js +31 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +86 -178
- package/dist/core/runner/session-rule-policy.js +4 -1
- package/dist/core/runner/teardown-bounded.d.ts +7 -0
- package/dist/core/runner/teardown-bounded.js +36 -0
- package/dist/core/runner/tool-disclosure.d.ts +2 -5
- package/dist/core/runner/turn-attachments.d.ts +5 -15
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +11 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/stub-env.d.ts +2 -6
- package/dist/core/surrogate-safe-slice.d.ts +4 -0
- package/dist/core/surrogate-safe-slice.js +18 -0
- package/dist/core/task-registry-monitor.js +15 -14
- package/dist/core/task-registry-shared.d.ts +22 -0
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +40 -57
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.js +2 -1
- package/dist/core/tool-policy.js +72 -19
- package/dist/core/tool-result-store.d.ts +1 -0
- package/dist/core/tool-result-store.js +17 -1
- package/dist/core/trace.d.ts +3 -9
- package/dist/core/types.d.ts +16 -12
- package/dist/core/untrusted-egress.js +7 -5
- package/dist/core/workflow-journal-store.d.ts +9 -20
- package/dist/core/workflow-run-store.d.ts +11 -10
- package/dist/core/workflow-run-store.js +22 -16
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/compaction/utils.js +4 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +13 -33
- package/dist/engine/harness/types.d.ts +29 -67
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +11 -4
- package/dist/engine/loop/types.d.ts +11 -9
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/engine/session/memory-repo.d.ts +2 -6
- package/dist/engine/session/session.d.ts +5 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/background-agent-store.d.ts +3 -12
- package/dist/stores/file/background-agent-store.js +3 -41
- package/dist/stores/file/checkpoint-store.js +6 -6
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +4 -6
- package/dist/stores/file/session-store.js +28 -1
- package/dist/stores/file/workflow-journal-store.d.ts +3 -11
- package/dist/stores/file/workflow-run-store.d.ts +3 -7
- package/dist/stores/file/workflow-run-store.js +4 -17
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +73 -21
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +407 -91
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- package/dist/tools/fs/notebook.d.ts +2 -5
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +43 -14
- package/dist/tools/fs/search.js +5 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +57 -9
- package/dist/tools/worktree.d.ts +4 -7
- package/dist/tools/worktree.js +235 -143
- package/package.json +9 -2
package/dist/core/hooks.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export interface Hooks {
|
|
|
7
7
|
userPromptSubmit?(prompt: string): UserPromptSubmitResult | undefined | Promise<UserPromptSubmitResult | undefined>;
|
|
8
8
|
stop?(ctx: StopHookContext): StopHookResult | undefined | Promise<StopHookResult | undefined>;
|
|
9
9
|
postToolUseFailure?(toolName: string, input: unknown, failure: HookToolFailure, ctx: HookToolContext): PostToolUseFailureResult | undefined | Promise<PostToolUseFailureResult | undefined>;
|
|
10
|
-
postToolBatch?(batch: PostToolBatchCall[]
|
|
10
|
+
postToolBatch?(batch: PostToolBatchCall[], meta?: {
|
|
11
|
+
injectedThisTurn: "final_verification" | "finalize";
|
|
12
|
+
}): PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>;
|
|
11
13
|
preCompact?(ctx: PreCompactContext): PreCompactResult | undefined | Promise<PreCompactResult | undefined>;
|
|
12
14
|
postCompact?(ctx: PostCompactContext): void | Promise<void>;
|
|
13
15
|
stopFailure?(ctx: StopFailureContext): void | Promise<void>;
|
|
@@ -121,6 +123,7 @@ export interface ToolGateInput {
|
|
|
121
123
|
approvalTimeoutMs?: number;
|
|
122
124
|
abortSignal?: AbortSignal;
|
|
123
125
|
permissionDenied?: (payload: PermissionDeniedPayload) => void | Promise<void>;
|
|
126
|
+
onHookError?: (err: unknown) => void;
|
|
124
127
|
shellGated?: boolean;
|
|
125
128
|
autoMode?: {
|
|
126
129
|
decider: import("./auto-mode.js").AutoModeDecider;
|
package/dist/core/hooks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decisionText } from "./tool-policy.js";
|
|
2
2
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
3
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
4
|
+
import { createSafeNotifier } from "./safe-notify.js";
|
|
4
5
|
export function cloneObserverInput(input) {
|
|
5
6
|
try {
|
|
6
7
|
return structuredClone(input);
|
|
@@ -16,6 +17,16 @@ export function cloneObserverInput(input) {
|
|
|
16
17
|
export function formatHookFeedback(text) {
|
|
17
18
|
return `<system-reminder>\n${text}\n</system-reminder>`;
|
|
18
19
|
}
|
|
20
|
+
function preToolUseCrashReason(subject, err) {
|
|
21
|
+
const raw = err instanceof Error ? err.message.trim() || err.name : String(err);
|
|
22
|
+
const cause = inlineUntrusted(raw, 200);
|
|
23
|
+
return (`a PreToolUse hook crashed while screening ${subject}; the call was NOT executed (fail-closed). ` +
|
|
24
|
+
`This is a failure of the deployment's hook, NOT of the tool itself — an identical retry reaches the ` +
|
|
25
|
+
`same hook and fails the same way. Hook error: ${cause || "(no message)"}`);
|
|
26
|
+
}
|
|
27
|
+
function traceHookCrash(input, err, notifier) {
|
|
28
|
+
notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
|
|
29
|
+
}
|
|
19
30
|
function withProbeTimeout(p, ms, signal) {
|
|
20
31
|
if (ms === undefined && signal === undefined)
|
|
21
32
|
return p;
|
|
@@ -48,8 +59,20 @@ export async function runToolGate(input) {
|
|
|
48
59
|
let currentInput = event.input;
|
|
49
60
|
const preToolContext = [];
|
|
50
61
|
let hookAsk;
|
|
62
|
+
const notifier = createSafeNotifier();
|
|
51
63
|
if (preToolUse) {
|
|
52
|
-
|
|
64
|
+
let r;
|
|
65
|
+
try {
|
|
66
|
+
r = await preToolUse(toolName, currentInput, { toolCallId, toolName });
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const reason = preToolUseCrashReason(`this call to "${toolName}"`, err);
|
|
70
|
+
traceHookCrash(input, err, notifier);
|
|
71
|
+
if (input.permissionDenied) {
|
|
72
|
+
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook" }), "toolGate.permissionDenied");
|
|
73
|
+
}
|
|
74
|
+
return { block: true, reason: formatHookFeedback(reason), preToolContext };
|
|
75
|
+
}
|
|
53
76
|
if (r) {
|
|
54
77
|
if (r.additionalContext) {
|
|
55
78
|
preToolContext.push(r.additionalContext);
|
|
@@ -182,7 +205,16 @@ export async function runToolGate(input) {
|
|
|
182
205
|
break;
|
|
183
206
|
}
|
|
184
207
|
if (preToolUse) {
|
|
185
|
-
|
|
208
|
+
let hr;
|
|
209
|
+
try {
|
|
210
|
+
hr = await preToolUse(toolName, editArgs, { toolCallId, toolName });
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
traceHookCrash(input, err, notifier);
|
|
214
|
+
editDenied = { action: "deny", reason: preToolUseCrashReason(`the approved edit for "${toolName}"`, err) };
|
|
215
|
+
denySource = "hook";
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
186
218
|
if (hr) {
|
|
187
219
|
if (hr.additionalContext)
|
|
188
220
|
preToolContext.push(hr.additionalContext);
|
|
@@ -250,11 +282,7 @@ export async function runToolGate(input) {
|
|
|
250
282
|
currentInput = decision.updatedInput;
|
|
251
283
|
}
|
|
252
284
|
if (input.permissionDenied) {
|
|
253
|
-
|
|
254
|
-
await input.permissionDenied({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource });
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
}
|
|
285
|
+
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource }), "toolGate.permissionDenied");
|
|
258
286
|
}
|
|
259
287
|
return {
|
|
260
288
|
block: true,
|
|
@@ -35,22 +35,28 @@ export class LspDiagnosticsRegistry {
|
|
|
35
35
|
}
|
|
36
36
|
drain() {
|
|
37
37
|
const out = [];
|
|
38
|
+
const requeued = new Map();
|
|
38
39
|
let total = 0;
|
|
39
40
|
for (const [uri, all] of this.pending) {
|
|
40
|
-
if (total >= MAX_TOTAL)
|
|
41
|
-
break;
|
|
42
41
|
const fresh = all
|
|
43
42
|
.filter((d) => !this.delivered.has(diagnosticKey(uri, d)))
|
|
44
|
-
.sort((a, b) => (a.severity ?? 99) - (b.severity ?? 99))
|
|
45
|
-
.slice(0, Math.min(MAX_PER_FILE, MAX_TOTAL - total));
|
|
43
|
+
.sort((a, b) => (a.severity ?? 99) - (b.severity ?? 99));
|
|
46
44
|
if (fresh.length === 0)
|
|
47
45
|
continue;
|
|
48
|
-
|
|
46
|
+
const room = Math.max(0, Math.min(MAX_PER_FILE, MAX_TOTAL - total));
|
|
47
|
+
const take = fresh.slice(0, room);
|
|
48
|
+
if (fresh.length > take.length)
|
|
49
|
+
requeued.set(uri, fresh.slice(take.length));
|
|
50
|
+
if (take.length === 0)
|
|
51
|
+
continue;
|
|
52
|
+
for (const d of take)
|
|
49
53
|
this.delivered.add(diagnosticKey(uri, d));
|
|
50
|
-
total +=
|
|
51
|
-
out.push({ uri, diagnostics:
|
|
54
|
+
total += take.length;
|
|
55
|
+
out.push({ uri, diagnostics: take });
|
|
52
56
|
}
|
|
53
57
|
this.pending.clear();
|
|
58
|
+
for (const [uri, rest] of requeued)
|
|
59
|
+
this.pending.set(uri, rest);
|
|
54
60
|
return out;
|
|
55
61
|
}
|
|
56
62
|
}
|
|
@@ -6,4 +6,4 @@ export declare function buildRequest(op: LspOperation, params: LspRequestParams)
|
|
|
6
6
|
method: string;
|
|
7
7
|
params: unknown;
|
|
8
8
|
} | undefined;
|
|
9
|
-
export declare function parseResult(op: LspOperation, raw: unknown): LspResult;
|
|
9
|
+
export declare function parseResult(op: LspOperation, raw: unknown, fallbackUri?: string): LspResult;
|
|
@@ -3,6 +3,11 @@ export function pathToUri(p) {
|
|
|
3
3
|
if (p.startsWith("/")) {
|
|
4
4
|
return `file://${p.split("/").map(encodeURIComponent).join("/")}`;
|
|
5
5
|
}
|
|
6
|
+
const drive = /^([A-Za-z]):[\\/](.*)$/.exec(p);
|
|
7
|
+
if (drive)
|
|
8
|
+
return `file:///${drive[1]}:/${drive[2].split(/[\\/]/).map(encodeURIComponent).join("/")}`;
|
|
9
|
+
if (p.startsWith("\\\\"))
|
|
10
|
+
return `file://${p.slice(2).split(/[\\/]/).map(encodeURIComponent).join("/")}`;
|
|
6
11
|
return pathToFileURL(p).href;
|
|
7
12
|
}
|
|
8
13
|
export function uriToPath(uri) {
|
|
@@ -47,7 +52,7 @@ export function buildRequest(op, params) {
|
|
|
47
52
|
return { method: "textDocument/prepareCallHierarchy", params: { ...td, position } };
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
|
-
export function parseResult(op, raw) {
|
|
55
|
+
export function parseResult(op, raw, fallbackUri) {
|
|
51
56
|
switch (op) {
|
|
52
57
|
case "goToDefinition":
|
|
53
58
|
case "findReferences":
|
|
@@ -62,7 +67,7 @@ export function parseResult(op, raw) {
|
|
|
62
67
|
case "documentSymbol":
|
|
63
68
|
case "workspaceSymbol":
|
|
64
69
|
case "prepareCallHierarchy": {
|
|
65
|
-
const symbols = toSymbols(raw);
|
|
70
|
+
const symbols = toSymbols(raw, op === "documentSymbol" ? fallbackUri : undefined);
|
|
66
71
|
return symbols.length ? { kind: "symbols", symbols } : { kind: "none" };
|
|
67
72
|
}
|
|
68
73
|
case "incomingCalls":
|
|
@@ -92,21 +97,28 @@ function toLocations(raw) {
|
|
|
92
97
|
})
|
|
93
98
|
.filter((l) => l !== undefined);
|
|
94
99
|
}
|
|
95
|
-
function toSymbols(raw) {
|
|
100
|
+
function toSymbols(raw, fallbackUri) {
|
|
96
101
|
const out = [];
|
|
97
|
-
const walk = (x,
|
|
102
|
+
const walk = (x, inheritedUri) => {
|
|
98
103
|
const loc = x.location;
|
|
99
|
-
const uri = loc?.uri ?? (typeof x.uri === "string" ? x.uri :
|
|
104
|
+
const uri = loc?.uri ?? (typeof x.uri === "string" ? x.uri : inheritedUri);
|
|
100
105
|
const range = (loc?.range ?? x.selectionRange ?? x.range);
|
|
101
106
|
if (typeof x.name === "string" && uri) {
|
|
102
|
-
const s = range?.start
|
|
103
|
-
out.push({
|
|
107
|
+
const s = range?.start;
|
|
108
|
+
out.push({
|
|
109
|
+
name: x.name,
|
|
110
|
+
...(symbolKindName(x.kind) ? { kind: symbolKindName(x.kind) } : {}),
|
|
111
|
+
uri,
|
|
112
|
+
line: (s?.line ?? 0) + 1,
|
|
113
|
+
character: s?.character ?? 0,
|
|
114
|
+
...(s === undefined ? { positionUnknown: true } : {}),
|
|
115
|
+
});
|
|
104
116
|
}
|
|
105
117
|
for (const child of asArray(x.children))
|
|
106
118
|
walk(child, uri);
|
|
107
119
|
};
|
|
108
120
|
for (const x of asArray(raw))
|
|
109
|
-
walk(x);
|
|
121
|
+
walk(x, fallbackUri);
|
|
110
122
|
return out;
|
|
111
123
|
}
|
|
112
124
|
function toCallSymbols(raw) {
|
|
@@ -11,17 +11,27 @@ export declare class TransportLspSession implements LspSession {
|
|
|
11
11
|
private readonly log;
|
|
12
12
|
private readonly warmup;
|
|
13
13
|
private readonly opened;
|
|
14
|
+
private readonly openedUris;
|
|
14
15
|
private readonly syncing;
|
|
15
16
|
private readonly createdAt;
|
|
16
|
-
private
|
|
17
|
+
private warmupBarrier?;
|
|
18
|
+
private warmupDone;
|
|
19
|
+
private warmupAttempts;
|
|
17
20
|
constructor(transport: LspTransport, languageId: string, readText: LspReadText, log?: (event: string, fields: Record<string, unknown>) => void, warmup?: SessionWarmup, diagnostics?: LspDiagnosticsRegistry);
|
|
21
|
+
get inFlight(): number;
|
|
22
|
+
private activeRequests;
|
|
18
23
|
request(op: LspOperation, params: LspRequestParams, signal?: AbortSignal): Promise<LspResult>;
|
|
24
|
+
private requestOnce;
|
|
25
|
+
private warmupBarrierOnce;
|
|
19
26
|
private syncOpenedFiles;
|
|
20
27
|
private syncOne;
|
|
21
28
|
private static classifyUnbranded;
|
|
22
29
|
get closed(): boolean;
|
|
23
30
|
openedFiles(): string[];
|
|
24
|
-
warmOpen(filePaths: string[], signal?: AbortSignal): Promise<
|
|
31
|
+
warmOpen(filePaths: string[], signal?: AbortSignal): Promise<{
|
|
32
|
+
opened: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
}>;
|
|
25
35
|
notifyFileChanged(filePath: string, signal?: AbortSignal): Promise<void>;
|
|
26
36
|
close(): Promise<void>;
|
|
27
37
|
}
|
package/dist/core/lsp-session.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { uriToPath, buildRequest, callHierarchyMethod, parseResult, pathToUri } from "./lsp-protocol.js";
|
|
2
|
-
import { lspFailureOf } from "./lsp.js";
|
|
2
|
+
import { lspFailureOf, SharedAbortScope, settleOnAbort } from "./lsp.js";
|
|
3
|
+
const WARMUP_MAX_ATTEMPTS = 3;
|
|
3
4
|
export class TransportLspSession {
|
|
4
5
|
transport;
|
|
5
6
|
languageId;
|
|
@@ -7,9 +8,12 @@ export class TransportLspSession {
|
|
|
7
8
|
log;
|
|
8
9
|
warmup;
|
|
9
10
|
opened = new Map();
|
|
11
|
+
openedUris = new Map();
|
|
10
12
|
syncing = new Map();
|
|
11
13
|
createdAt = Date.now();
|
|
12
|
-
|
|
14
|
+
warmupBarrier;
|
|
15
|
+
warmupDone = false;
|
|
16
|
+
warmupAttempts = 0;
|
|
13
17
|
constructor(transport, languageId, readText, log = () => { }, warmup = { windowMs: 10_000, retryMs: 1_000 }, diagnostics) {
|
|
14
18
|
this.transport = transport;
|
|
15
19
|
this.languageId = languageId;
|
|
@@ -24,7 +28,7 @@ export class TransportLspSession {
|
|
|
24
28
|
if (!p || typeof p.uri !== "string" || !Array.isArray(p.diagnostics))
|
|
25
29
|
return;
|
|
26
30
|
if (typeof p.version === "number") {
|
|
27
|
-
const filePath = uriToPath(p.uri);
|
|
31
|
+
const filePath = this.openedUris.get(p.uri) ?? uriToPath(p.uri);
|
|
28
32
|
const known = this.opened.get(filePath);
|
|
29
33
|
if (known && p.version < known.version) {
|
|
30
34
|
this.log("lsp_diagnostics_stale_dropped", { uri: p.uri, version: p.version, current: known.version });
|
|
@@ -35,18 +39,34 @@ export class TransportLspSession {
|
|
|
35
39
|
});
|
|
36
40
|
}
|
|
37
41
|
}
|
|
42
|
+
get inFlight() {
|
|
43
|
+
return this.activeRequests;
|
|
44
|
+
}
|
|
45
|
+
activeRequests = 0;
|
|
38
46
|
async request(op, params, signal) {
|
|
47
|
+
this.activeRequests += 1;
|
|
48
|
+
try {
|
|
49
|
+
return await this.requestOnce(op, params, signal);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
this.activeRequests -= 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async requestOnce(op, params, signal) {
|
|
39
56
|
const built = buildRequest(op, params);
|
|
40
57
|
if (!built)
|
|
41
58
|
return { kind: "none", reason: "unsupported_operation" };
|
|
42
|
-
await this.syncOpenedFiles(params.filePath, signal);
|
|
43
|
-
if (!
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
const sync = await this.syncOpenedFiles(params.filePath, signal);
|
|
60
|
+
if (!sync.ok) {
|
|
61
|
+
const reason = signal?.aborted ? "cancelled" : (sync.reason ?? "file_sync_failed");
|
|
62
|
+
this.log("lsp_op_degraded", { op, file: params.filePath, reason, err: sync.error });
|
|
63
|
+
return { kind: "none", reason, ...(reason === "file_sync_failed" ? { detail: sync.error } : {}) };
|
|
64
|
+
}
|
|
65
|
+
if (this.warmup.windowMs > 0 && !this.warmupDone) {
|
|
66
|
+
if (op === "goToDefinition")
|
|
67
|
+
this.warmupDone = true;
|
|
68
|
+
else
|
|
69
|
+
await this.warmupBarrierOnce(params.filePath, signal);
|
|
50
70
|
}
|
|
51
71
|
for (let attempt = 0;; attempt++) {
|
|
52
72
|
let raw;
|
|
@@ -66,7 +86,7 @@ export class TransportLspSession {
|
|
|
66
86
|
}
|
|
67
87
|
const result = noHierarchyItem
|
|
68
88
|
? { kind: "none", reason: "no_call_hierarchy_item" }
|
|
69
|
-
: parseResult(op, raw);
|
|
89
|
+
: parseResult(op, raw, pathToUri(params.filePath));
|
|
70
90
|
if (result.kind === "none" && this.warmup.windowMs > 0 && Date.now() - this.createdAt < this.warmup.windowMs && !signal?.aborted) {
|
|
71
91
|
await new Promise((r) => setTimeout(r, this.warmup.retryMs));
|
|
72
92
|
continue;
|
|
@@ -84,47 +104,90 @@ export class TransportLspSession {
|
|
|
84
104
|
return result;
|
|
85
105
|
}
|
|
86
106
|
}
|
|
107
|
+
async warmupBarrierOnce(filePath, signal) {
|
|
108
|
+
let entry = this.warmupBarrier;
|
|
109
|
+
if (entry && entry.scope.signal.aborted)
|
|
110
|
+
entry = undefined;
|
|
111
|
+
if (!entry) {
|
|
112
|
+
if (this.warmupAttempts >= WARMUP_MAX_ATTEMPTS)
|
|
113
|
+
return;
|
|
114
|
+
this.warmupAttempts += 1;
|
|
115
|
+
const scope = new SharedAbortScope();
|
|
116
|
+
const started = {};
|
|
117
|
+
const job = this.transport
|
|
118
|
+
.request("textDocument/definition", { textDocument: { uri: pathToUri(filePath) }, position: { line: 0, character: 0 } }, scope.signal)
|
|
119
|
+
.then(() => {
|
|
120
|
+
this.warmupDone = true;
|
|
121
|
+
})
|
|
122
|
+
.catch(() => {
|
|
123
|
+
if (this.warmupBarrier === started.self)
|
|
124
|
+
this.warmupBarrier = undefined;
|
|
125
|
+
});
|
|
126
|
+
entry = { job, scope };
|
|
127
|
+
started.self = entry;
|
|
128
|
+
this.warmupBarrier = entry;
|
|
129
|
+
}
|
|
130
|
+
entry.scope.join(signal);
|
|
131
|
+
await settleOnAbort(entry.job, signal, () => undefined);
|
|
132
|
+
}
|
|
87
133
|
async syncOpenedFiles(queried, signal) {
|
|
88
134
|
const files = new Set(this.opened.keys());
|
|
89
135
|
files.add(queried);
|
|
90
|
-
await Promise.all([...files].map((fp) => this.syncOne(fp, signal)));
|
|
136
|
+
const outcomes = await Promise.all([...files].map(async (fp) => [fp, await this.syncOne(fp, signal)]));
|
|
137
|
+
return outcomes.find(([fp]) => fp === queried)?.[1] ?? { ok: true };
|
|
91
138
|
}
|
|
92
139
|
syncOne(filePath, signal) {
|
|
93
140
|
const inflight = this.syncing.get(filePath);
|
|
94
|
-
if (inflight)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
if (inflight && !inflight.scope.signal.aborted) {
|
|
142
|
+
inflight.scope.join(signal);
|
|
143
|
+
return settleOnAbort(inflight.job, signal, () => ({ ok: false, error: "aborted by the caller" }));
|
|
144
|
+
}
|
|
145
|
+
const scope = new SharedAbortScope();
|
|
146
|
+
scope.join(signal);
|
|
147
|
+
const entry = {
|
|
148
|
+
scope,
|
|
149
|
+
job: (async () => {
|
|
150
|
+
let text;
|
|
151
|
+
try {
|
|
152
|
+
text = await this.readText(filePath, scope.signal);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
156
|
+
this.log("lsp_sync_skipped", { file: filePath, error, opened: this.opened.has(filePath) });
|
|
157
|
+
return { ok: false, error };
|
|
158
|
+
}
|
|
159
|
+
const cur = this.opened.get(filePath);
|
|
160
|
+
const uri = pathToUri(filePath);
|
|
161
|
+
if (!cur) {
|
|
162
|
+
this.transport.notify("textDocument/didOpen", {
|
|
163
|
+
textDocument: { uri, languageId: this.languageId, version: 1, text },
|
|
164
|
+
});
|
|
165
|
+
if (this.transport.closed === true) {
|
|
166
|
+
this.log("lsp_sync_notify_dropped", { file: filePath, kind: "didOpen" });
|
|
167
|
+
return { ok: false, error: "didOpen was not delivered (transport closed)", reason: "server_terminated" };
|
|
168
|
+
}
|
|
169
|
+
this.opened.set(filePath, { version: 1, text });
|
|
170
|
+
this.openedUris.set(uri, filePath);
|
|
171
|
+
}
|
|
172
|
+
else if (cur.text !== text) {
|
|
173
|
+
const version = cur.version + 1;
|
|
174
|
+
this.transport.notify("textDocument/didChange", { textDocument: { uri, version }, contentChanges: [{ text }] });
|
|
175
|
+
if (this.transport.closed === true) {
|
|
176
|
+
this.log("lsp_sync_notify_dropped", { file: filePath, kind: "didChange", version });
|
|
177
|
+
return { ok: false, error: "didChange was not delivered (transport closed)", reason: "server_terminated" };
|
|
178
|
+
}
|
|
179
|
+
this.opened.set(filePath, { version, text });
|
|
180
|
+
this.log("lsp_file_resync", { file: filePath, version });
|
|
181
|
+
}
|
|
182
|
+
return { ok: true };
|
|
183
|
+
})(),
|
|
184
|
+
};
|
|
185
|
+
this.syncing.set(filePath, entry);
|
|
186
|
+
const settled = entry.job.finally(() => {
|
|
187
|
+
if (this.syncing.get(filePath) === entry)
|
|
188
|
+
this.syncing.delete(filePath);
|
|
189
|
+
});
|
|
190
|
+
return settleOnAbort(settled, signal, () => ({ ok: false, error: "aborted by the caller" }));
|
|
128
191
|
}
|
|
129
192
|
static classifyUnbranded(signal, transport) {
|
|
130
193
|
if (signal?.aborted)
|
|
@@ -140,10 +203,12 @@ export class TransportLspSession {
|
|
|
140
203
|
return [...this.opened.keys()];
|
|
141
204
|
}
|
|
142
205
|
async warmOpen(filePaths, signal) {
|
|
143
|
-
await Promise.all(filePaths.map((fp) => this.syncOne(fp, signal)));
|
|
206
|
+
const outcomes = await Promise.all(filePaths.map((fp) => this.syncOne(fp, signal)));
|
|
207
|
+
const failed = outcomes.filter((o) => !o.ok).length;
|
|
208
|
+
return { opened: outcomes.length - failed, failed };
|
|
144
209
|
}
|
|
145
210
|
notifyFileChanged(filePath, signal) {
|
|
146
|
-
return this.syncOne(filePath, signal);
|
|
211
|
+
return this.syncOne(filePath, signal).then(() => undefined);
|
|
147
212
|
}
|
|
148
213
|
close() {
|
|
149
214
|
return this.transport.close();
|
package/dist/core/lsp.d.ts
CHANGED
|
@@ -13,8 +13,9 @@ export interface LspSymbolInfo {
|
|
|
13
13
|
uri: string;
|
|
14
14
|
line: number;
|
|
15
15
|
character: number;
|
|
16
|
+
positionUnknown?: boolean;
|
|
16
17
|
}
|
|
17
|
-
export type LspNoneReason = "unsupported_operation" | "server_error" | "timeout" | "cancelled" | "server_terminated" | "no_call_hierarchy_item";
|
|
18
|
+
export type LspNoneReason = "unsupported_operation" | "server_error" | "timeout" | "cancelled" | "server_terminated" | "no_call_hierarchy_item" | "file_sync_failed";
|
|
18
19
|
export type LspResult = {
|
|
19
20
|
kind: "locations";
|
|
20
21
|
locations: LspLocation[];
|
|
@@ -35,6 +36,16 @@ export declare function lspFailureOf(e: unknown): {
|
|
|
35
36
|
reason: LspNoneReason;
|
|
36
37
|
detail?: string;
|
|
37
38
|
} | undefined;
|
|
39
|
+
export declare class SharedAbortScope {
|
|
40
|
+
private readonly controller;
|
|
41
|
+
private joiners;
|
|
42
|
+
private abandoned;
|
|
43
|
+
get signal(): AbortSignal;
|
|
44
|
+
join(signal?: AbortSignal): void;
|
|
45
|
+
private abandon;
|
|
46
|
+
}
|
|
47
|
+
export declare function settleOnAbort<T>(job: Promise<T>, signal: AbortSignal | undefined, onAbort: () => T): Promise<T>;
|
|
48
|
+
export declare function resolveLspPath(filePath: string, cwd?: string): string;
|
|
38
49
|
export interface LspRequestParams {
|
|
39
50
|
filePath: string;
|
|
40
51
|
line?: number;
|
|
@@ -55,6 +66,7 @@ export type LspReadText = (filePath: string, signal?: AbortSignal) => Promise<st
|
|
|
55
66
|
export declare const MAX_LSP_FILE_BYTES: number;
|
|
56
67
|
export interface LspServerManager {
|
|
57
68
|
sessionFor(filePath: string, signal?: AbortSignal, env?: ExecutionEnv): Promise<LspSession | undefined>;
|
|
69
|
+
unavailableReason?(filePath: string, env?: ExecutionEnv): string | undefined;
|
|
58
70
|
readonly diagnostics?: import("./lsp-diagnostics.js").LspDiagnosticsRegistry;
|
|
59
71
|
}
|
|
60
72
|
export interface LspToolOptions {
|
package/dist/core/lsp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { uriToPath } from "./lsp-protocol.js";
|
|
2
|
+
import { foldRedundantPathSegments, isAbsolutePathForm } from "../tools/fs/safety.js";
|
|
2
3
|
import { Type } from "typebox";
|
|
3
4
|
import { defineTool, errorResult } from "./tools.js";
|
|
4
5
|
export const LSP_OPERATIONS = [
|
|
@@ -8,6 +9,7 @@ export const LSP_OPERATIONS = [
|
|
|
8
9
|
export const LSP_FAILURE_BRAND = "lspFailureReason";
|
|
9
10
|
const LSP_NONE_REASONS = new Set([
|
|
10
11
|
"unsupported_operation", "server_error", "timeout", "cancelled", "server_terminated", "no_call_hierarchy_item",
|
|
12
|
+
"file_sync_failed",
|
|
11
13
|
]);
|
|
12
14
|
export function brandLspFailure(e, reason, detail) {
|
|
13
15
|
const carrier = e;
|
|
@@ -23,6 +25,54 @@ export function lspFailureOf(e) {
|
|
|
23
25
|
const d = e.lspFailureDetail;
|
|
24
26
|
return { reason: r, ...(typeof d === "string" ? { detail: d } : {}) };
|
|
25
27
|
}
|
|
28
|
+
export class SharedAbortScope {
|
|
29
|
+
controller = new AbortController();
|
|
30
|
+
joiners = 0;
|
|
31
|
+
abandoned = 0;
|
|
32
|
+
get signal() {
|
|
33
|
+
return this.controller.signal;
|
|
34
|
+
}
|
|
35
|
+
join(signal) {
|
|
36
|
+
this.joiners += 1;
|
|
37
|
+
if (!signal)
|
|
38
|
+
return;
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
this.abandon();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
signal.addEventListener("abort", () => this.abandon(), { once: true });
|
|
44
|
+
}
|
|
45
|
+
abandon() {
|
|
46
|
+
this.abandoned += 1;
|
|
47
|
+
if (this.abandoned >= this.joiners)
|
|
48
|
+
this.controller.abort();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function settleOnAbort(job, signal, onAbort) {
|
|
52
|
+
if (!signal)
|
|
53
|
+
return job;
|
|
54
|
+
if (signal.aborted) {
|
|
55
|
+
void job.catch(() => undefined);
|
|
56
|
+
return Promise.resolve(onAbort());
|
|
57
|
+
}
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const onAbortEvent = () => resolve(onAbort());
|
|
60
|
+
signal.addEventListener("abort", onAbortEvent, { once: true });
|
|
61
|
+
job.then((v) => {
|
|
62
|
+
signal.removeEventListener("abort", onAbortEvent);
|
|
63
|
+
resolve(v);
|
|
64
|
+
}, (e) => {
|
|
65
|
+
signal.removeEventListener("abort", onAbortEvent);
|
|
66
|
+
reject(e);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export function resolveLspPath(filePath, cwd) {
|
|
71
|
+
const isAbsForm = isAbsolutePathForm(filePath);
|
|
72
|
+
if (isAbsForm || !cwd)
|
|
73
|
+
return foldRedundantPathSegments(filePath);
|
|
74
|
+
return foldRedundantPathSegments(cwd.replace(/[\\/]+$/, "") + (cwd.includes("\\") ? "\\" : "/") + filePath);
|
|
75
|
+
}
|
|
26
76
|
export const MAX_LSP_FILE_BYTES = 10 * 1000 * 1000;
|
|
27
77
|
function fmt(result, ctx) {
|
|
28
78
|
switch (result.kind) {
|
|
@@ -37,7 +87,9 @@ function fmt(result, ctx) {
|
|
|
37
87
|
return result.symbols.length === 0
|
|
38
88
|
? "No symbols found."
|
|
39
89
|
: `Found ${result.symbols.length} symbol(s):\n` +
|
|
40
|
-
result.symbols
|
|
90
|
+
result.symbols
|
|
91
|
+
.map((s) => `- ${s.name}${s.kind ? ` (${s.kind})` : ""} — ${s.uri}${s.positionUnknown ? " (no position reported by the language server)" : `:${s.line}:${s.character}`}`)
|
|
92
|
+
.join("\n");
|
|
41
93
|
case "none":
|
|
42
94
|
return fmtNone(result, ctx);
|
|
43
95
|
}
|
|
@@ -60,9 +112,14 @@ function fmtNone(result, ctx) {
|
|
|
60
112
|
return `Error performing ${operation}: the request was cancelled before the language server answered. This is NOT an empty result.`;
|
|
61
113
|
case "server_error":
|
|
62
114
|
return `Error performing ${operation}: ${detail ?? "the language server returned an error"}. This is NOT an empty result — fall back to Grep/Read.`;
|
|
115
|
+
case "file_sync_failed":
|
|
116
|
+
return `Error performing ${operation}: "${filePath}" could not be read to synchronize it with the language server${detail ? ` (${detail})` : ""}. This is NOT an empty result — the server was never given the file's current text; re-check the path or fall back to Grep/Read.`;
|
|
63
117
|
}
|
|
64
118
|
}
|
|
65
|
-
const LSP_ERROR_REASONS = new Set([
|
|
119
|
+
const LSP_ERROR_REASONS = new Set([
|
|
120
|
+
"server_error", "timeout", "cancelled", "server_terminated",
|
|
121
|
+
"file_sync_failed",
|
|
122
|
+
]);
|
|
66
123
|
async function filterIgnored(result, isIgnored) {
|
|
67
124
|
if (!isIgnored)
|
|
68
125
|
return result;
|
|
@@ -129,11 +186,9 @@ export function createLspTool(manager, opts = {}) {
|
|
|
129
186
|
if (operation === "workspaceSymbol" && wsQuery === undefined) {
|
|
130
187
|
return errorResult(`Error (LSP): the "workspaceSymbol" operation needs a query.`);
|
|
131
188
|
}
|
|
189
|
+
const target = resolveLspPath(filePath, opts.env?.cwd);
|
|
132
190
|
if (opts.env) {
|
|
133
|
-
const
|
|
134
|
-
const cwd = opts.env.cwd;
|
|
135
|
-
const probe = isAbsForm || !cwd ? filePath : cwd.replace(/[\\/]+$/, "") + (cwd.includes("\\") ? "\\" : "/") + filePath;
|
|
136
|
-
const info = await opts.env.fileInfo(probe, ctx.signal);
|
|
191
|
+
const info = await opts.env.fileInfo(target, ctx.signal);
|
|
137
192
|
if (!info.ok && info.error.code === "not_found")
|
|
138
193
|
return errorResult(`Error (LSP): File does not exist: ${filePath}`);
|
|
139
194
|
if (!info.ok && info.error.code === "invalid")
|
|
@@ -144,12 +199,13 @@ export function createLspTool(manager, opts = {}) {
|
|
|
144
199
|
return `File too large for LSP analysis (${Math.ceil(info.value.size / 1e6)}MB exceeds 10MB limit)`;
|
|
145
200
|
}
|
|
146
201
|
}
|
|
147
|
-
const session = await manager.sessionFor(
|
|
202
|
+
const session = await manager.sessionFor(target, ctx.signal, opts.env);
|
|
148
203
|
if (!session) {
|
|
149
|
-
|
|
204
|
+
const why = manager.unavailableReason?.(target, opts.env) ?? "no language server for its language in this environment";
|
|
205
|
+
return `LSP is unavailable for "${filePath}" (${why}). Fall back to Grep/Read for this lookup.`;
|
|
150
206
|
}
|
|
151
207
|
const seamCharacter = character !== undefined ? character - 1 : undefined;
|
|
152
|
-
const result = await session.request(operation, { filePath, line, character: seamCharacter, symbol: wsQuery }, ctx.signal);
|
|
208
|
+
const result = await session.request(operation, { filePath: target, line, character: seamCharacter, symbol: wsQuery }, ctx.signal);
|
|
153
209
|
const final = toModelBase(await filterIgnored(result, opts.isPathIgnored));
|
|
154
210
|
const text = fmt(final, { operation, filePath });
|
|
155
211
|
return final.kind === "none" && final.reason !== undefined && LSP_ERROR_REASONS.has(final.reason) ? errorResult(text) : text;
|
|
@@ -8,12 +8,13 @@ export interface MailboxLease {
|
|
|
8
8
|
messages: MailboxMessage[];
|
|
9
9
|
maxSeq: number;
|
|
10
10
|
}
|
|
11
|
+
export interface MailboxAppendMessage {
|
|
12
|
+
from?: string;
|
|
13
|
+
content: string;
|
|
14
|
+
sentAt: number;
|
|
15
|
+
}
|
|
11
16
|
export interface MailboxStore {
|
|
12
|
-
append(scope: string, handle: string, msg:
|
|
13
|
-
from?: string;
|
|
14
|
-
content: string;
|
|
15
|
-
sentAt: number;
|
|
16
|
-
}): Promise<number>;
|
|
17
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
17
18
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
18
19
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
19
20
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
@@ -28,11 +29,7 @@ export declare class InMemoryMailboxStore implements MailboxStore {
|
|
|
28
29
|
private boxes;
|
|
29
30
|
private key;
|
|
30
31
|
private box;
|
|
31
|
-
append(scope: string, handle: string, msg:
|
|
32
|
-
from?: string;
|
|
33
|
-
content: string;
|
|
34
|
-
sentAt: number;
|
|
35
|
-
}): Promise<number>;
|
|
32
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
36
33
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
37
34
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
38
35
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|