@oscharko-dev/keiko-server 0.2.7 → 0.2.9
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/.tsbuildinfo +1 -1
- package/dist/chat-handlers.d.ts +18 -2
- package/dist/chat-handlers.d.ts.map +1 -1
- package/dist/chat-handlers.js +185 -3
- package/dist/command-runner-errors.d.ts +17 -0
- package/dist/command-runner-errors.d.ts.map +1 -0
- package/dist/command-runner-errors.js +37 -0
- package/dist/command-runner-evidence.d.ts +23 -0
- package/dist/command-runner-evidence.d.ts.map +1 -0
- package/dist/command-runner-evidence.js +69 -0
- package/dist/command-runner-routes.d.ts +7 -0
- package/dist/command-runner-routes.d.ts.map +1 -0
- package/dist/command-runner-routes.js +175 -0
- package/dist/command-runner.d.ts +29 -0
- package/dist/command-runner.d.ts.map +1 -0
- package/dist/command-runner.js +348 -0
- package/dist/conversation-prompt.d.ts +2 -2
- package/dist/conversation-prompt.d.ts.map +1 -1
- package/dist/conversation-prompt.js +17 -1
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +3 -0
- package/dist/deps.d.ts +28 -1
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +288 -13
- package/dist/discussion-prompt.d.ts +4 -0
- package/dist/discussion-prompt.d.ts.map +1 -0
- package/dist/discussion-prompt.js +19 -0
- package/dist/editor/agentActionAudit.d.ts +18 -0
- package/dist/editor/agentActionAudit.d.ts.map +1 -0
- package/dist/editor/agentActionAudit.js +80 -0
- package/dist/editor/agentRoutes.d.ts +1 -0
- package/dist/editor/agentRoutes.d.ts.map +1 -1
- package/dist/editor/agentRoutes.js +292 -55
- package/dist/editor/agentSessionRegistry.d.ts +35 -0
- package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
- package/dist/editor/agentSessionRegistry.js +243 -0
- package/dist/editor/completionRoutes.d.ts.map +1 -1
- package/dist/editor/completionRoutes.js +5 -10
- package/dist/editor/languageRoutes.d.ts +12 -1
- package/dist/editor/languageRoutes.d.ts.map +1 -1
- package/dist/editor/languageRoutes.js +71 -8
- package/dist/editor/languageService.d.ts +3 -2
- package/dist/editor/languageService.d.ts.map +1 -1
- package/dist/editor/languageService.js +41 -3
- package/dist/editor/languageServiceHost.d.ts.map +1 -1
- package/dist/editor/languageServiceHost.js +2 -2
- package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
- package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageOperation.js +436 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageProviders.js +161 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
- package/dist/editor/lsp/lspFrameCodec.js +164 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
- package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
- package/dist/editor/lsp/lspLanguageProvider.js +29 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
- package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
- package/dist/editor/lsp/lspNodeAdapter.js +230 -0
- package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
- package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
- package/dist/editor/lsp/lspProcessManager.js +255 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
- package/dist/editor/lsp/lspRestartThrottle.js +24 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
- package/dist/editor/lsp/lspStatusRoute.js +22 -0
- package/dist/editor/lsp/lspTransport.d.ts +19 -0
- package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
- package/dist/editor/lsp/lspTransport.js +55 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
- package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
- package/dist/files.d.ts +63 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +799 -1
- package/dist/gateway-readiness.d.ts +6 -0
- package/dist/gateway-readiness.d.ts.map +1 -0
- package/dist/gateway-readiness.js +624 -0
- package/dist/gateway-setup.d.ts +2 -0
- package/dist/gateway-setup.d.ts.map +1 -1
- package/dist/gateway-setup.js +275 -11
- package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
- package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetProjection.js +206 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetRoutes.js +293 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
- package/dist/gitDelivery/commitRoutes.d.ts +23 -0
- package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/commitRoutes.js +204 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/evidenceRoutes.js +101 -0
- package/dist/gitDelivery/execution.d.ts +38 -0
- package/dist/gitDelivery/execution.d.ts.map +1 -0
- package/dist/gitDelivery/execution.js +117 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/localMutationRoutes.js +165 -0
- package/dist/gitDelivery/mergeExecution.d.ts +63 -0
- package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
- package/dist/gitDelivery/mergeExecution.js +168 -0
- package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
- package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/mergeRoutes.js +218 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
- package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
- package/dist/gitDelivery/prExecution.d.ts +54 -0
- package/dist/gitDelivery/prExecution.d.ts.map +1 -0
- package/dist/gitDelivery/prExecution.js +192 -0
- package/dist/gitDelivery/prRoutes.d.ts +12 -0
- package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/prRoutes.js +256 -0
- package/dist/gitDelivery/pushExecution.d.ts +43 -0
- package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
- package/dist/gitDelivery/pushExecution.js +124 -0
- package/dist/gitDelivery/pushRoutes.d.ts +12 -0
- package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/pushRoutes.js +200 -0
- package/dist/gitDelivery/requestGuards.d.ts +15 -0
- package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
- package/dist/gitDelivery/requestGuards.js +97 -0
- package/dist/gitDelivery/syncEvidence.d.ts +37 -0
- package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
- package/dist/gitDelivery/syncEvidence.js +85 -0
- package/dist/gitDelivery/syncExecution.d.ts +30 -0
- package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
- package/dist/gitDelivery/syncExecution.js +266 -0
- package/dist/gitDelivery/syncRoutes.d.ts +13 -0
- package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/syncRoutes.js +200 -0
- package/dist/gitPorcelainStatus.d.ts +15 -0
- package/dist/gitPorcelainStatus.d.ts.map +1 -0
- package/dist/gitPorcelainStatus.js +104 -0
- package/dist/gitRepositoryReads.d.ts +10 -0
- package/dist/gitRepositoryReads.d.ts.map +1 -0
- package/dist/gitRepositoryReads.js +314 -0
- package/dist/gitRepositoryRoutes.d.ts +7 -0
- package/dist/gitRepositoryRoutes.d.ts.map +1 -0
- package/dist/gitRepositoryRoutes.js +221 -0
- package/dist/gitRoutes.d.ts +66 -0
- package/dist/gitRoutes.d.ts.map +1 -0
- package/dist/gitRoutes.js +543 -0
- package/dist/governed-workflow.d.ts +2 -0
- package/dist/governed-workflow.d.ts.map +1 -1
- package/dist/governed-workflow.js +4 -0
- package/dist/grounded-qa-hybrid.d.ts.map +1 -1
- package/dist/grounded-qa-hybrid.js +2 -0
- package/dist/grounded-qa-multi-source.d.ts.map +1 -1
- package/dist/grounded-qa-multi-source.js +1 -0
- package/dist/grounded-qa.d.ts +11 -0
- package/dist/grounded-qa.d.ts.map +1 -1
- package/dist/grounded-qa.js +14 -4
- package/dist/headers.d.ts +4 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +11 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
- package/dist/local-knowledge-grounded-qa.js +11 -2
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
- package/dist/read-handlers.d.ts +5 -0
- package/dist/read-handlers.d.ts.map +1 -1
- package/dist/read-handlers.js +57 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +260 -12
- package/dist/run-engine.d.ts.map +1 -1
- package/dist/run-engine.js +3 -0
- package/dist/run-handlers.d.ts +0 -1
- package/dist/run-handlers.d.ts.map +1 -1
- package/dist/run-handlers.js +64 -211
- package/dist/run-request.d.ts +11 -0
- package/dist/run-request.d.ts.map +1 -1
- package/dist/run-request.js +158 -10
- package/dist/runtime/capabilityDetector.d.ts +38 -0
- package/dist/runtime/capabilityDetector.d.ts.map +1 -0
- package/dist/runtime/capabilityDetector.js +443 -0
- package/dist/runtime/capabilityRoutes.d.ts +9 -0
- package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
- package/dist/runtime/capabilityRoutes.js +45 -0
- package/dist/runtime/containerEngineDetector.d.ts +17 -0
- package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
- package/dist/runtime/containerEngineDetector.js +222 -0
- package/dist/runtime/containerRoutes.d.ts +8 -0
- package/dist/runtime/containerRoutes.d.ts.map +1 -0
- package/dist/runtime/containerRoutes.js +207 -0
- package/dist/runtime/containerRunner-errors.d.ts +18 -0
- package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
- package/dist/runtime/containerRunner-errors.js +42 -0
- package/dist/runtime/containerRunner-evidence.d.ts +24 -0
- package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
- package/dist/runtime/containerRunner-evidence.js +74 -0
- package/dist/runtime/containerRunner.d.ts +37 -0
- package/dist/runtime/containerRunner.d.ts.map +1 -0
- package/dist/runtime/containerRunner.js +443 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -4
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +2 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/messages.d.ts +2 -1
- package/dist/store/messages.d.ts.map +1 -1
- package/dist/store/messages.js +46 -4
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +68 -1
- package/dist/store/types.d.ts +3 -2
- package/dist/store/types.d.ts.map +1 -1
- package/dist/task-workspace/active-store.d.ts +21 -0
- package/dist/task-workspace/active-store.d.ts.map +1 -0
- package/dist/task-workspace/active-store.js +55 -0
- package/dist/task-workspace/authorization.d.ts +7 -0
- package/dist/task-workspace/authorization.d.ts.map +1 -0
- package/dist/task-workspace/authorization.js +54 -0
- package/dist/task-workspace/binding.d.ts +3 -0
- package/dist/task-workspace/binding.d.ts.map +1 -0
- package/dist/task-workspace/binding.js +22 -0
- package/dist/task-workspace/cleanup.d.ts +4 -0
- package/dist/task-workspace/cleanup.d.ts.map +1 -0
- package/dist/task-workspace/cleanup.js +428 -0
- package/dist/task-workspace/errors.d.ts +14 -0
- package/dist/task-workspace/errors.d.ts.map +1 -0
- package/dist/task-workspace/errors.js +81 -0
- package/dist/task-workspace/evidence.d.ts +32 -0
- package/dist/task-workspace/evidence.d.ts.map +1 -0
- package/dist/task-workspace/evidence.js +52 -0
- package/dist/task-workspace/field-safety.d.ts +3 -0
- package/dist/task-workspace/field-safety.d.ts.map +1 -0
- package/dist/task-workspace/field-safety.js +42 -0
- package/dist/task-workspace/health.d.ts +4 -0
- package/dist/task-workspace/health.d.ts.map +1 -0
- package/dist/task-workspace/health.js +163 -0
- package/dist/task-workspace/lifecycle.d.ts +3 -0
- package/dist/task-workspace/lifecycle.d.ts.map +1 -0
- package/dist/task-workspace/lifecycle.js +248 -0
- package/dist/task-workspace/locks.d.ts +13 -0
- package/dist/task-workspace/locks.d.ts.map +1 -0
- package/dist/task-workspace/locks.js +44 -0
- package/dist/task-workspace/managed-root.d.ts +7 -0
- package/dist/task-workspace/managed-root.d.ts.map +1 -0
- package/dist/task-workspace/managed-root.js +98 -0
- package/dist/task-workspace/mutex.d.ts +8 -0
- package/dist/task-workspace/mutex.d.ts.map +1 -0
- package/dist/task-workspace/mutex.js +82 -0
- package/dist/task-workspace/naming.d.ts +15 -0
- package/dist/task-workspace/naming.d.ts.map +1 -0
- package/dist/task-workspace/naming.js +0 -0
- package/dist/task-workspace/provisioning.d.ts +3 -0
- package/dist/task-workspace/provisioning.d.ts.map +1 -0
- package/dist/task-workspace/provisioning.js +528 -0
- package/dist/task-workspace/reconciliation.d.ts +15 -0
- package/dist/task-workspace/reconciliation.d.ts.map +1 -0
- package/dist/task-workspace/reconciliation.js +274 -0
- package/dist/task-workspace/repair.d.ts +3 -0
- package/dist/task-workspace/repair.d.ts.map +1 -0
- package/dist/task-workspace/repair.js +286 -0
- package/dist/task-workspace/routes.d.ts +19 -0
- package/dist/task-workspace/routes.d.ts.map +1 -0
- package/dist/task-workspace/routes.js +481 -0
- package/dist/task-workspace/store.d.ts +12 -0
- package/dist/task-workspace/store.d.ts.map +1 -0
- package/dist/task-workspace/store.js +128 -0
- package/dist/task-workspace/types.d.ts +170 -0
- package/dist/task-workspace/types.d.ts.map +1 -0
- package/dist/task-workspace/types.js +5 -0
- package/dist/voice-action-governance.d.ts +23 -0
- package/dist/voice-action-governance.d.ts.map +1 -0
- package/dist/voice-action-governance.js +126 -0
- package/dist/voice-handlers.d.ts +6 -0
- package/dist/voice-handlers.d.ts.map +1 -0
- package/dist/voice-handlers.js +570 -0
- package/dist/voice-realtime-grounded-tool.d.ts +31 -0
- package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
- package/dist/voice-realtime-grounded-tool.js +322 -0
- package/dist/voice-realtime.d.ts +69 -0
- package/dist/voice-realtime.d.ts.map +1 -0
- package/dist/voice-realtime.js +787 -0
- package/dist/workspace-state-handlers.d.ts +5 -0
- package/dist/workspace-state-handlers.d.ts.map +1 -0
- package/dist/workspace-state-handlers.js +106 -0
- package/package.json +20 -19
- package/dist/grounded-handoff.d.ts +0 -4
- package/dist/grounded-handoff.d.ts.map +0 -1
- package/dist/grounded-handoff.js +0 -445
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// Issue #1392 — live editor-agent session registry, bounded action queue, and SSE event bus.
|
|
2
|
+
//
|
|
3
|
+
// The server COORDINATES; the browser bridge OWNS live editor mutation (ADR-0060). This module is the
|
|
4
|
+
// single owner of the in-memory, non-persistent control-plane state that the BFF routes operate over:
|
|
5
|
+
//
|
|
6
|
+
// 1. Session registry — the latest snapshot each browser bridge has published (bounded).
|
|
7
|
+
// 2. Bridge liveness — which sessions currently hold at least one live SSE connection. A queued
|
|
8
|
+
// action is only meaningful when a live bridge can execute it; otherwise the
|
|
9
|
+
// route answers a structured NO_ACTIVE_BRIDGE conflict (AC1).
|
|
10
|
+
// 3. Bounded action queue — in-flight actions awaiting a browser result, each armed with a timeout so
|
|
11
|
+
// a silent bridge cannot strand the queue (AC2). The queue is keyed PER
|
|
12
|
+
// SESSION so a result, timeout, or re-queue for one session can never touch
|
|
13
|
+
// another session's slots; the per-session depth is bounded (perf).
|
|
14
|
+
// 4. Event bus — fan-out of session/action/result events, scoped per session so an action
|
|
15
|
+
// for one session never reaches another session's bridge (bounded fan-out).
|
|
16
|
+
//
|
|
17
|
+
// The registry NEVER mutates editor/React state and NEVER executes an action — it records, bounds,
|
|
18
|
+
// times out, and fans out (AC5). It produces only post-admission *lifecycle* failures (TIMED_OUT /
|
|
19
|
+
// QUEUE_FULL); preflight *conflicts* are the route's policy. No raw source content (snapshot text,
|
|
20
|
+
// edits, patch bodies) is logged here. The factory mirrors the `createRunRegistry` idiom: a small
|
|
21
|
+
// state record plus module-level operations, so each operation stays short and explicitly typed.
|
|
22
|
+
import { EDITOR_AGENT_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
|
|
23
|
+
// A queued action the bridge never acknowledges within ACTION_TIMEOUT_MS is failed and evicted so the
|
|
24
|
+
// bounded queue self-heals (AC2). MAX_QUEUED_PER_SESSION bounds the in-flight depth per session;
|
|
25
|
+
// MAX_SESSIONS bounds the snapshot registry so a long-lived server cannot grow it without limit.
|
|
26
|
+
export const EDITOR_AGENT_ACTION_TIMEOUT_MS = 15_000;
|
|
27
|
+
export const EDITOR_AGENT_MAX_QUEUED_PER_SESSION = 64;
|
|
28
|
+
export const EDITOR_AGENT_MAX_SESSIONS = 256;
|
|
29
|
+
function defaultSetTimer(handler, ms) {
|
|
30
|
+
const handle = setTimeout(handler, ms);
|
|
31
|
+
handle.unref?.();
|
|
32
|
+
return handle;
|
|
33
|
+
}
|
|
34
|
+
function defaultClearTimer(handle) {
|
|
35
|
+
clearTimeout(handle);
|
|
36
|
+
}
|
|
37
|
+
function lifecycleFailure(action, code, message) {
|
|
38
|
+
return {
|
|
39
|
+
schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
|
|
40
|
+
actionId: action.actionId,
|
|
41
|
+
sessionId: action.sessionId,
|
|
42
|
+
status: "failed",
|
|
43
|
+
message,
|
|
44
|
+
failure: { code, message },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function eventSessionId(event) {
|
|
48
|
+
switch (event.type) {
|
|
49
|
+
case "session":
|
|
50
|
+
return event.snapshot.sessionId;
|
|
51
|
+
case "action":
|
|
52
|
+
return event.action.sessionId;
|
|
53
|
+
case "result":
|
|
54
|
+
return event.result.sessionId;
|
|
55
|
+
case "heartbeat":
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Bounded fan-out: a session-scoped event reaches the global observers plus that session's own bridge
|
|
60
|
+
// connections, never every bridge. A session-less event (heartbeat) reaches everyone.
|
|
61
|
+
function deliver(state, event) {
|
|
62
|
+
for (const send of state.observers)
|
|
63
|
+
send(event);
|
|
64
|
+
const sessionId = eventSessionId(event);
|
|
65
|
+
if (sessionId === undefined) {
|
|
66
|
+
for (const set of state.bridges.values()) {
|
|
67
|
+
for (const send of set)
|
|
68
|
+
send(event);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const set = state.bridges.get(sessionId);
|
|
73
|
+
if (set === undefined)
|
|
74
|
+
return;
|
|
75
|
+
for (const send of set)
|
|
76
|
+
send(event);
|
|
77
|
+
}
|
|
78
|
+
function emit(state, payload) {
|
|
79
|
+
state.eventSeq += 1;
|
|
80
|
+
deliver(state, {
|
|
81
|
+
schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
|
|
82
|
+
eventId: `editor-agent-${String(state.eventSeq)}`,
|
|
83
|
+
...payload,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function clearPending(state, sessionId, actionId) {
|
|
87
|
+
const inner = state.pending.get(sessionId);
|
|
88
|
+
const entry = inner?.get(actionId);
|
|
89
|
+
if (inner === undefined || entry === undefined)
|
|
90
|
+
return false;
|
|
91
|
+
state.clearTimer(entry.timer);
|
|
92
|
+
inner.delete(actionId);
|
|
93
|
+
if (inner.size === 0)
|
|
94
|
+
state.pending.delete(sessionId);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
function onTimeout(state, action) {
|
|
98
|
+
const inner = state.pending.get(action.sessionId);
|
|
99
|
+
if (inner?.get(action.actionId) === undefined)
|
|
100
|
+
return;
|
|
101
|
+
inner.delete(action.actionId);
|
|
102
|
+
if (inner.size === 0)
|
|
103
|
+
state.pending.delete(action.sessionId);
|
|
104
|
+
emit(state, {
|
|
105
|
+
type: "result",
|
|
106
|
+
result: lifecycleFailure(action, "TIMED_OUT", "The browser bridge did not report a result before the deadline."),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function connectImpl(state, sessionId, send) {
|
|
110
|
+
if (sessionId === undefined) {
|
|
111
|
+
state.observers.add(send);
|
|
112
|
+
return () => {
|
|
113
|
+
state.observers.delete(send);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
let set = state.bridges.get(sessionId);
|
|
117
|
+
if (set === undefined) {
|
|
118
|
+
set = new Set();
|
|
119
|
+
state.bridges.set(sessionId, set);
|
|
120
|
+
}
|
|
121
|
+
set.add(send);
|
|
122
|
+
return () => {
|
|
123
|
+
const current = state.bridges.get(sessionId);
|
|
124
|
+
if (current === undefined)
|
|
125
|
+
return;
|
|
126
|
+
current.delete(send);
|
|
127
|
+
if (current.size === 0)
|
|
128
|
+
state.bridges.delete(sessionId);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function queueActionImpl(state, action, emitAction) {
|
|
132
|
+
const inner = state.pending.get(action.sessionId);
|
|
133
|
+
if (inner?.get(action.actionId) !== undefined) {
|
|
134
|
+
// A second admit for an actionId already in flight would otherwise strand the first action's
|
|
135
|
+
// deadline; reject it instead so the first action still self-heals (AC2). Pure rejection — the
|
|
136
|
+
// existing slot/timer are untouched.
|
|
137
|
+
return {
|
|
138
|
+
kind: "rejected",
|
|
139
|
+
result: {
|
|
140
|
+
schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
|
|
141
|
+
actionId: action.actionId,
|
|
142
|
+
sessionId: action.sessionId,
|
|
143
|
+
status: "failed",
|
|
144
|
+
message: "An action with this id is already in flight for this session.",
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if ((inner?.size ?? 0) >= state.maxQueuedPerSession) {
|
|
149
|
+
return {
|
|
150
|
+
kind: "rejected",
|
|
151
|
+
result: lifecycleFailure(action, "QUEUE_FULL", "The editor action queue for this session is full; retry once an action completes."),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const slots = inner ?? new Map();
|
|
155
|
+
if (inner === undefined)
|
|
156
|
+
state.pending.set(action.sessionId, slots);
|
|
157
|
+
const timer = state.setTimer(() => {
|
|
158
|
+
onTimeout(state, action);
|
|
159
|
+
}, state.actionTimeoutMs);
|
|
160
|
+
slots.set(action.actionId, { timer });
|
|
161
|
+
emit(state, { type: "action", action: emitAction });
|
|
162
|
+
return {
|
|
163
|
+
kind: "queued",
|
|
164
|
+
result: {
|
|
165
|
+
schemaVersion: EDITOR_AGENT_SCHEMA_VERSION,
|
|
166
|
+
actionId: action.actionId,
|
|
167
|
+
sessionId: action.sessionId,
|
|
168
|
+
status: "queued",
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function reportResultImpl(state, result) {
|
|
173
|
+
// Correlate strictly by (sessionId, actionId): a result can only resolve its own session's slot, so
|
|
174
|
+
// a mismatched or cross-session result is fanned out for visibility without disturbing the queue.
|
|
175
|
+
clearPending(state, result.sessionId, result.actionId);
|
|
176
|
+
emit(state, { type: "result", result });
|
|
177
|
+
}
|
|
178
|
+
// Bound the snapshot registry: when over the cap, evict the oldest session that is neither bridged nor
|
|
179
|
+
// has in-flight actions (so active work is never disrupted), preferring the just-registered session
|
|
180
|
+
// last. If every other session is busy the registry is left slightly over the soft cap.
|
|
181
|
+
function evictOldestIdleSession(state, keepSessionId) {
|
|
182
|
+
for (const sessionId of state.sessions.keys()) {
|
|
183
|
+
if (sessionId === keepSessionId)
|
|
184
|
+
continue;
|
|
185
|
+
if ((state.bridges.get(sessionId)?.size ?? 0) > 0)
|
|
186
|
+
continue;
|
|
187
|
+
if ((state.pending.get(sessionId)?.size ?? 0) > 0)
|
|
188
|
+
continue;
|
|
189
|
+
state.sessions.delete(sessionId);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function resetImpl(state) {
|
|
194
|
+
for (const inner of state.pending.values()) {
|
|
195
|
+
for (const entry of inner.values())
|
|
196
|
+
state.clearTimer(entry.timer);
|
|
197
|
+
}
|
|
198
|
+
state.sessions.clear();
|
|
199
|
+
state.bridges.clear();
|
|
200
|
+
state.observers.clear();
|
|
201
|
+
state.pending.clear();
|
|
202
|
+
state.eventSeq = 0;
|
|
203
|
+
}
|
|
204
|
+
export function createEditorAgentRegistry(options = {}) {
|
|
205
|
+
const state = {
|
|
206
|
+
sessions: new Map(),
|
|
207
|
+
bridges: new Map(),
|
|
208
|
+
observers: new Set(),
|
|
209
|
+
pending: new Map(),
|
|
210
|
+
actionTimeoutMs: options.actionTimeoutMs ?? EDITOR_AGENT_ACTION_TIMEOUT_MS,
|
|
211
|
+
maxQueuedPerSession: options.maxQueuedPerSession ?? EDITOR_AGENT_MAX_QUEUED_PER_SESSION,
|
|
212
|
+
maxSessions: options.maxSessions ?? EDITOR_AGENT_MAX_SESSIONS,
|
|
213
|
+
setTimer: options.setTimer ?? defaultSetTimer,
|
|
214
|
+
clearTimer: options.clearTimer ?? defaultClearTimer,
|
|
215
|
+
eventSeq: 0,
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
registerSnapshot: (snapshot) => {
|
|
219
|
+
state.sessions.set(snapshot.sessionId, snapshot);
|
|
220
|
+
if (state.sessions.size > state.maxSessions) {
|
|
221
|
+
evictOldestIdleSession(state, snapshot.sessionId);
|
|
222
|
+
}
|
|
223
|
+
emit(state, { type: "session", snapshot });
|
|
224
|
+
},
|
|
225
|
+
listSessions: () => [...state.sessions.values()],
|
|
226
|
+
snapshotFor: (sessionId) => state.sessions.get(sessionId),
|
|
227
|
+
selectSnapshot: (sessionId) => sessionId === undefined ? [...state.sessions.values()][0] : state.sessions.get(sessionId),
|
|
228
|
+
hasLiveBridge: (sessionId) => (state.bridges.get(sessionId)?.size ?? 0) > 0,
|
|
229
|
+
liveBridgeCount: (sessionId) => state.bridges.get(sessionId)?.size ?? 0,
|
|
230
|
+
connect: (sessionId, send) => connectImpl(state, sessionId, send),
|
|
231
|
+
queueAction: (action, emitAction) => queueActionImpl(state, action, emitAction),
|
|
232
|
+
reportResult: (result) => {
|
|
233
|
+
reportResultImpl(state, result);
|
|
234
|
+
},
|
|
235
|
+
pendingCount: (sessionId) => state.pending.get(sessionId)?.size ?? 0,
|
|
236
|
+
reset: () => {
|
|
237
|
+
resetImpl(state);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Process-wide singleton used by the BFF routes. Unit tests construct isolated instances with injected
|
|
242
|
+
// timers; the route integration/security tests share this singleton and reset it between cases.
|
|
243
|
+
export const editorAgentRegistry = createEditorAgentRegistry();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completionRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/completionRoutes.ts"],"names":[],"mappings":"AAkBA,OAAO,EAqBL,KAAK,qBAAqB,EAG3B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"completionRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/completionRoutes.ts"],"names":[],"mappings":"AAkBA,OAAO,EAqBL,KAAK,qBAAqB,EAG3B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAWtE,OAAO,EAIL,KAAK,WAAW,EAEjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,6BAA6B,CAAC;AASrC,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;CAGrC,CAAC;AAGX,mGAAmG;AACnG,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,WAAW,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACzD,kGAAkG;IAClG,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC1D,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,mGAAmG;IACnG,QAAQ,CAAC,qBAAqB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACnE,4FAA4F;IAC5F,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1D;AA2fD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,WAAW,CAAC,CA8CtB"}
|
|
@@ -17,15 +17,13 @@
|
|
|
17
17
|
// Model Gateway, retrieval, or any provider directly (Acceptance Criterion 5).
|
|
18
18
|
import { CODING_CONTEXT_SCHEMA_VERSION, CODING_CONTEXT_BUDGETS, DEFAULT_LANGUAGE_SERVICE_LIMITS, EDITOR_COMPLETION_SCHEMA_VERSION, isValidScopePath, parseEditorCompletionRequest, stripUnsafeFormatChars, toCodingContextWirePack, } from "@oscharko-dev/keiko-contracts";
|
|
19
19
|
import { Gateway, selectCompletionModel } from "@oscharko-dev/keiko-model-gateway";
|
|
20
|
-
import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
|
|
21
20
|
import { errorBody } from "../routes.js";
|
|
22
21
|
import { currentGatewayConfig } from "../deps.js";
|
|
23
22
|
import { readJsonObject, resolveRoot, runFilesHandler } from "../files.js";
|
|
24
23
|
import { assembleCodingContext } from "./codingContext.js";
|
|
25
24
|
import { recordCodingContextEvidence } from "./codingContextEvidence.js";
|
|
26
25
|
import { recordEditorCompletionModelEvidence } from "./completionModelEvidence.js";
|
|
27
|
-
import { clientAbortSignal, resolveOverlayPath, STATUS_BY_CODE } from "./languageRoutes.js";
|
|
28
|
-
import { runLanguageOperation } from "./languageService.js";
|
|
26
|
+
import { clientAbortSignal, resolveOverlayPath, runEditorLanguageOperation, STATUS_BY_CODE, } from "./languageRoutes.js";
|
|
29
27
|
import { buildModelCompletionPrompt, generateModelCompletions, } from "./editorCompletionModel.js";
|
|
30
28
|
import { estimateEditorModelReservationTokens, settleModelUsage, sharedEditorModelTokenBudget, } from "./editorModelTokenBudget.js";
|
|
31
29
|
// The overlay buffer may be up to the document-size cap; allow 64 KiB of JSON envelope on top.
|
|
@@ -347,18 +345,14 @@ function buildWireResponse(deterministic, model) {
|
|
|
347
345
|
provenance: buildProvenance(model, producedModelItems),
|
|
348
346
|
};
|
|
349
347
|
}
|
|
350
|
-
function runDeterministicCompletion(input) {
|
|
348
|
+
async function runDeterministicCompletion(input) {
|
|
351
349
|
const langRequest = {
|
|
352
350
|
operation: "completion",
|
|
353
351
|
root: input.request.root,
|
|
354
352
|
document: input.request.document,
|
|
355
353
|
position: input.request.position,
|
|
356
354
|
};
|
|
357
|
-
const outcome =
|
|
358
|
-
fs: nodeWorkspaceFs,
|
|
359
|
-
realRoot: input.realRoot,
|
|
360
|
-
overlayAbsolutePath: input.overlayAbsolutePath,
|
|
361
|
-
signal: input.signal,
|
|
355
|
+
const outcome = await runEditorLanguageOperation(langRequest, input.deps, input.realRoot, input.overlayAbsolutePath, input.signal, {
|
|
362
356
|
limits: input.limits ?? COMPLETION_LANGUAGE_SERVICE_LIMITS,
|
|
363
357
|
now: input.now,
|
|
364
358
|
});
|
|
@@ -393,8 +387,9 @@ export async function handleEditorCompletion(ctx, deps, options = {}) {
|
|
|
393
387
|
}
|
|
394
388
|
const signal = clientAbortSignal(ctx);
|
|
395
389
|
// Tier 1: deterministic language-service completion (always).
|
|
396
|
-
const deterministic = runDeterministicCompletion({
|
|
390
|
+
const deterministic = await runDeterministicCompletion({
|
|
397
391
|
request: sanitizedRequest,
|
|
392
|
+
deps,
|
|
398
393
|
realRoot: root.realRoot,
|
|
399
394
|
overlayAbsolutePath,
|
|
400
395
|
signal,
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import { type LanguageServiceErrorCode, type LanguageServiceLimits } from "@oscharko-dev/keiko-contracts";
|
|
1
|
+
import { type LanguageProviderDescriptor, type LanguageServiceErrorCode, type LanguageServiceLimits, type LanguageServiceRequest } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import type { CommandRule } from "@oscharko-dev/keiko-tools";
|
|
2
3
|
import { type RouteContext, type RouteResult } from "../routes.js";
|
|
3
4
|
import type { UiHandlerDeps } from "../deps.js";
|
|
5
|
+
import type { LanguageServiceOutcome } from "./languageService.js";
|
|
6
|
+
import type { LspSpawnFn } from "./lsp/lspNodeAdapter.js";
|
|
4
7
|
export interface EditorLanguageRouteOptions {
|
|
5
8
|
/** Test-only deterministic limits seam; production keeps the default deadline. */
|
|
6
9
|
readonly limits?: LanguageServiceLimits | undefined;
|
|
7
10
|
/** Test-only deterministic clock seam; production keeps the real clock. */
|
|
8
11
|
readonly now?: (() => number) | undefined;
|
|
12
|
+
/** Test-only provider descriptor override seam for capabilities route coverage. */
|
|
13
|
+
readonly capabilityDescriptorOverrides?: readonly LanguageProviderDescriptor[] | undefined;
|
|
14
|
+
/** Test-only command-policy seam for host-provider detection. */
|
|
15
|
+
readonly hostLanguageCommandRules?: readonly CommandRule[] | undefined;
|
|
16
|
+
/** Test-only LSP spawn seam for host-provider operation coverage. */
|
|
17
|
+
readonly hostLanguageSpawn?: LspSpawnFn | undefined;
|
|
9
18
|
}
|
|
10
19
|
export declare const STATUS_BY_CODE: Readonly<Record<LanguageServiceErrorCode, number>>;
|
|
11
20
|
export declare function resolveOverlayPath(realRoot: string, relativePath: string): string;
|
|
12
21
|
export declare function clientAbortSignal(ctx: RouteContext): AbortSignal;
|
|
13
22
|
export declare function handleEditorLanguage(ctx: RouteContext, deps: UiHandlerDeps, options?: EditorLanguageRouteOptions): Promise<RouteResult>;
|
|
14
23
|
export declare function handleEditorLanguageCapabilities(): RouteResult;
|
|
24
|
+
export declare function runEditorLanguageOperation(request: LanguageServiceRequest, deps: UiHandlerDeps, realRoot: string, overlayAbsolutePath: string, signal: AbortSignal, options?: EditorLanguageRouteOptions): Promise<LanguageServiceOutcome>;
|
|
25
|
+
export declare function handleEditorLanguageCapabilitiesForRoute(ctx: RouteContext, deps: UiHandlerDeps, options?: EditorLanguageRouteOptions): Promise<RouteResult>;
|
|
15
26
|
//# sourceMappingURL=languageRoutes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languageRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/languageRoutes.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"languageRoutes.d.ts","sourceRoot":"","sources":["../../src/editor/languageRoutes.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAMnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAK1D,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,mFAAmF;IACnF,QAAQ,CAAC,6BAA6B,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAC;IAC3F,iEAAiE;IACjE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,CAAC;IACvE,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACrD;AAID,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAQ7E,CAAC;AAaF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWjF;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,WAAW,CAahE;AAiBD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED,wBAAgB,gCAAgC,IAAI,WAAW,CAE9D;AA0BD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,sBAAsB,CAAC,CAsBjC;AAED,wBAAsB,wCAAwC,CAC5D,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC,CAuBtB"}
|
|
@@ -13,6 +13,8 @@ import { errorBody } from "../routes.js";
|
|
|
13
13
|
import { FilesError, readJsonObject, resolveRoot, runFilesHandler } from "../files.js";
|
|
14
14
|
import { DENIED_MESSAGE, pathIsDenied } from "../files-deny.js";
|
|
15
15
|
import { describeLanguageCapabilities, runLanguageOperation } from "./languageService.js";
|
|
16
|
+
import { detectHostLanguageProviderDescriptors, HOST_LANGUAGE_PROVIDER_SPECS, } from "./lsp/hostLanguageProviders.js";
|
|
17
|
+
import { runHostLanguageOperation } from "./lsp/hostLanguageOperation.js";
|
|
16
18
|
// The overlay buffer may be up to the document-size cap; allow 64 KiB of JSON envelope on top.
|
|
17
19
|
const MAX_LANGUAGE_BODY_BYTES = DEFAULT_LANGUAGE_SERVICE_LIMITS.maxDocumentBytes + 64 * 1024;
|
|
18
20
|
// Exported for reuse by the completion route (#1199), which maps the same deterministic
|
|
@@ -90,17 +92,78 @@ export async function handleEditorLanguage(ctx, deps, options = {}) {
|
|
|
90
92
|
return runFilesHandler(async () => {
|
|
91
93
|
const root = await resolveRoot(deps.store, request.root, deps.redactor);
|
|
92
94
|
const overlayAbsolutePath = resolveOverlayPath(root.realRoot, request.document.path);
|
|
93
|
-
const outcome =
|
|
94
|
-
fs: nodeWorkspaceFs,
|
|
95
|
-
realRoot: root.realRoot,
|
|
96
|
-
overlayAbsolutePath,
|
|
97
|
-
signal: clientAbortSignal(ctx),
|
|
98
|
-
limits: options.limits,
|
|
99
|
-
now: options.now,
|
|
100
|
-
});
|
|
95
|
+
const outcome = await runEditorLanguageOperation(request, deps, root.realRoot, overlayAbsolutePath, clientAbortSignal(ctx), options);
|
|
101
96
|
return outcomeToResult(outcome, deps);
|
|
102
97
|
});
|
|
103
98
|
}
|
|
104
99
|
export function handleEditorLanguageCapabilities() {
|
|
105
100
|
return { status: 200, body: describeLanguageCapabilities() };
|
|
106
101
|
}
|
|
102
|
+
function defaultHostLanguageCommandRules() {
|
|
103
|
+
const names = new Set();
|
|
104
|
+
for (const spec of HOST_LANGUAGE_PROVIDER_SPECS) {
|
|
105
|
+
names.add(spec.executableName);
|
|
106
|
+
for (const executable of spec.requiredExecutables)
|
|
107
|
+
names.add(executable);
|
|
108
|
+
}
|
|
109
|
+
return [...names].sort().map((executable) => ({ executable }));
|
|
110
|
+
}
|
|
111
|
+
function workspaceForRoot(realRoot) {
|
|
112
|
+
return {
|
|
113
|
+
root: realRoot,
|
|
114
|
+
name: undefined,
|
|
115
|
+
version: undefined,
|
|
116
|
+
testFramework: "unknown",
|
|
117
|
+
sourceDirs: [],
|
|
118
|
+
testDirs: [],
|
|
119
|
+
languages: [],
|
|
120
|
+
ignoreLines: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export async function runEditorLanguageOperation(request, deps, realRoot, overlayAbsolutePath, signal, options = {}) {
|
|
124
|
+
const hostOutcome = await runHostLanguageOperation(request, {
|
|
125
|
+
workspace: workspaceForRoot(realRoot),
|
|
126
|
+
processEnv: deps.env,
|
|
127
|
+
commandRules: options.hostLanguageCommandRules ?? defaultHostLanguageCommandRules(),
|
|
128
|
+
overlayAbsolutePath,
|
|
129
|
+
signal,
|
|
130
|
+
limits: options.limits,
|
|
131
|
+
now: options.now,
|
|
132
|
+
...(options.hostLanguageSpawn !== undefined ? { spawn: options.hostLanguageSpawn } : {}),
|
|
133
|
+
});
|
|
134
|
+
if (hostOutcome !== undefined) {
|
|
135
|
+
return hostOutcome;
|
|
136
|
+
}
|
|
137
|
+
return runLanguageOperation(request, {
|
|
138
|
+
fs: nodeWorkspaceFs,
|
|
139
|
+
realRoot,
|
|
140
|
+
overlayAbsolutePath,
|
|
141
|
+
signal,
|
|
142
|
+
limits: options.limits,
|
|
143
|
+
now: options.now,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export async function handleEditorLanguageCapabilitiesForRoute(ctx, deps, options = {}) {
|
|
147
|
+
const root = ctx.url.searchParams.get("root")?.trim() ?? "";
|
|
148
|
+
if (root.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
status: 200,
|
|
151
|
+
body: describeLanguageCapabilities(undefined, options.capabilityDescriptorOverrides),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return runFilesHandler(async () => {
|
|
155
|
+
const resolved = await resolveRoot(deps.store, root, deps.redactor);
|
|
156
|
+
const detected = detectHostLanguageProviderDescriptors({
|
|
157
|
+
workspace: workspaceForRoot(resolved.realRoot),
|
|
158
|
+
processEnv: deps.env,
|
|
159
|
+
commandRules: options.hostLanguageCommandRules ?? defaultHostLanguageCommandRules(),
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
status: 200,
|
|
163
|
+
body: describeLanguageCapabilities(undefined, [
|
|
164
|
+
...detected,
|
|
165
|
+
...(options.capabilityDescriptorOverrides ?? []),
|
|
166
|
+
]),
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type LanguageCompletionResult, type LanguageDiagnosticsResult, type LanguageFormattingResult, type LanguageHoverResult, type LanguageServiceCapabilities, type LanguageServiceErrorCode, type LanguageServiceLimits, type LanguageServiceRequest, type LanguageSymbolResult } from "@oscharko-dev/keiko-contracts";
|
|
1
|
+
import { type LanguageCompletionResult, type LanguageDiagnosticsResult, type LanguageFormattingResult, type LanguageHoverResult, type LanguageServiceCapabilities, type LanguageServiceErrorCode, type LanguageProviderDescriptor, type LanguageServiceLimits, type LanguageServiceRequest, type LanguageSymbolResult } from "@oscharko-dev/keiko-contracts";
|
|
2
2
|
import type { WorkspaceFs } from "@oscharko-dev/keiko-workspace";
|
|
3
3
|
import { type LanguageProviderRegistry } from "./languageProvider.js";
|
|
4
4
|
export declare function languageServiceRegistry(): LanguageProviderRegistry;
|
|
5
|
-
export declare
|
|
5
|
+
export declare const NO_PROVIDER_UNAVAILABLE_REASON: "No language provider is configured for this language.";
|
|
6
|
+
export declare function describeLanguageCapabilities(registry?: LanguageProviderRegistry, descriptorOverrides?: readonly LanguageProviderDescriptor[]): LanguageServiceCapabilities;
|
|
6
7
|
export type LanguageServiceOutcome = {
|
|
7
8
|
readonly kind: "diagnostics";
|
|
8
9
|
readonly result: LanguageDiagnosticsResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languageService.d.ts","sourceRoot":"","sources":["../../src/editor/languageService.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"languageService.d.ts","sourceRoot":"","sources":["../../src/editor/languageService.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAG1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,uBAAuB,CAAC;AAsB/B,wBAAgB,uBAAuB,IAAI,wBAAwB,CAElE;AAKD,eAAO,MAAM,8BAA8B,EACzC,uDAAgE,CAAC;AASnE,wBAAgB,4BAA4B,CAC1C,QAAQ,GAAE,wBAA0C,EACpD,mBAAmB,GAAE,SAAS,0BAA0B,EAAO,GAC9D,2BAA2B,CA+B7B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IAEzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;CAC1D;AAsCD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,2BAA2B,GACnC,sBAAsB,CAsCxB"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// cancellation token, and sanitises every result for browser display. It never calls a model and
|
|
5
5
|
// never routes through the Model Gateway. Provider registration is the only thing that changes to
|
|
6
6
|
// add a language (#1213), so this orchestrator stays language-agnostic.
|
|
7
|
-
import { DEFAULT_LANGUAGE_SERVICE_LIMITS, LANGUAGE_SERVICE_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
|
|
7
|
+
import { DEFAULT_LANGUAGE_SERVICE_LIMITS, EDITOR_LANGUAGE_MODE_IDS, LANGUAGE_SERVICE_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
|
|
8
8
|
import { createDeadlineCancellation, isCancellation } from "./languageCancellation.js";
|
|
9
9
|
import { createLanguageProviderRegistry, } from "./languageProvider.js";
|
|
10
10
|
import { createTypescriptLanguageProvider } from "./typescriptLanguageProvider.js";
|
|
@@ -16,10 +16,48 @@ const defaultRegistry = createLanguageProviderRegistry([createTypescriptLanguage
|
|
|
16
16
|
export function languageServiceRegistry() {
|
|
17
17
|
return defaultRegistry;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
// Issue #1379 AC2 (ADR-0067 D3) — the content-free reason advertised for a known mode-map language
|
|
20
|
+
// that no registered provider serves. Distinct from the external-LSP "no executable configured"
|
|
21
|
+
// reason: this language has no provider at all, real or unavailable.
|
|
22
|
+
export const NO_PROVIDER_UNAVAILABLE_REASON = "No language provider is configured for this language.";
|
|
23
|
+
// Issue #1379 AC2 (ADR-0067 D3) — exhaustive over the canonical mode-map universe. We project the
|
|
24
|
+
// registered descriptors (available providers + unavailable external LSP descriptors), then append a
|
|
25
|
+
// synthetic `unavailable` descriptor for every known mode-map language that no descriptor already
|
|
26
|
+
// covers. After this, `providerForLanguage` returns a descriptor for EVERY known language id, never
|
|
27
|
+
// null, so AC2 holds at the registry layer rather than by UI patching. The synthesis is additive and
|
|
28
|
+
// read-only — it changes no operation-execution path. `runLanguageOperation` is untouched: an
|
|
29
|
+
// unmatched/unavailable language still yields UNSUPPORTED_LANGUAGE (AC3).
|
|
30
|
+
export function describeLanguageCapabilities(registry = defaultRegistry, descriptorOverrides = []) {
|
|
31
|
+
const overridesById = new Map(descriptorOverrides.map((descriptor) => [descriptor.id, descriptor]));
|
|
32
|
+
const descriptors = registry
|
|
33
|
+
.describe()
|
|
34
|
+
.map((descriptor) => overridesById.get(descriptor.id) ?? descriptor);
|
|
35
|
+
for (const descriptor of descriptorOverrides) {
|
|
36
|
+
if (!descriptors.some((entry) => entry.id === descriptor.id)) {
|
|
37
|
+
descriptors.push(descriptor);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const covered = new Set();
|
|
41
|
+
for (const descriptor of descriptors) {
|
|
42
|
+
for (const language of descriptor.languages) {
|
|
43
|
+
covered.add(language);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const synthesized = [];
|
|
47
|
+
for (const languageId of EDITOR_LANGUAGE_MODE_IDS) {
|
|
48
|
+
if (covered.has(languageId))
|
|
49
|
+
continue;
|
|
50
|
+
synthesized.push({
|
|
51
|
+
id: "none",
|
|
52
|
+
languages: [languageId],
|
|
53
|
+
operations: [],
|
|
54
|
+
availability: "unavailable",
|
|
55
|
+
unavailableReason: NO_PROVIDER_UNAVAILABLE_REASON,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
20
58
|
return {
|
|
21
59
|
schemaVersion: LANGUAGE_SERVICE_SCHEMA_VERSION,
|
|
22
|
-
providers:
|
|
60
|
+
providers: [...descriptors, ...synthesized],
|
|
23
61
|
};
|
|
24
62
|
}
|
|
25
63
|
function errorOutcome(code, message) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languageServiceHost.d.ts","sourceRoot":"","sources":["../../src/editor/languageServiceHost.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,+BAA+B,CAAC;AAEhF,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,qBAAqB,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CACrD;
|
|
1
|
+
{"version":3,"file":"languageServiceHost.d.ts","sourceRoot":"","sources":["../../src/editor/languageServiceHost.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,+BAA+B,CAAC;AAEhF,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,qBAAqB,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CACrD;AA8OD,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,oBAAoB,GAC5B,EAAE,CAAC,mBAAmB,CAiDxB"}
|
|
@@ -153,7 +153,7 @@ function createContainedReaders(fs, realRoot, overlayPath, overlayText, libDir,
|
|
|
153
153
|
}
|
|
154
154
|
// Resolves the compiler options for the overlay: the nearest tsconfig inside the root (following
|
|
155
155
|
// `extends` through the contained reader, never enumerating files) merged over safe defaults.
|
|
156
|
-
function resolveCompilerOptions(
|
|
156
|
+
function resolveCompilerOptions(overlayPath, readers) {
|
|
157
157
|
const defaults = defaultCompilerOptions();
|
|
158
158
|
const configPath = ts.findConfigFile(dirname(overlayPath), readers.fileExists, "tsconfig.json");
|
|
159
159
|
if (configPath === undefined || readers.containedReal(configPath) === undefined) {
|
|
@@ -207,7 +207,7 @@ export function createContainedLanguageServiceHost(options) {
|
|
|
207
207
|
const limits = options.limits ?? DEFAULT_LANGUAGE_SERVICE_LIMITS;
|
|
208
208
|
const libDir = normalizeSlashes(dirname(ts.getDefaultLibFilePath(defaultCompilerOptions())));
|
|
209
209
|
const readers = createContainedReaders(fs, realRoot, overlayPath, overlayText, libDir, cancellation, limits);
|
|
210
|
-
const compilerOptions = resolveCompilerOptions(
|
|
210
|
+
const compilerOptions = resolveCompilerOptions(overlayPath, readers);
|
|
211
211
|
const overlay = normalizeSlashes(overlayPath);
|
|
212
212
|
return {
|
|
213
213
|
getCompilationSettings: () => compilerOptions,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type LanguageServiceLimits, type LanguageServiceRequest } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import type { CommandRule } from "@oscharko-dev/keiko-tools";
|
|
3
|
+
import type { WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
|
|
4
|
+
import type { LanguageServiceOutcome } from "../languageService.js";
|
|
5
|
+
import { type LspSpawnFn } from "./lspNodeAdapter.js";
|
|
6
|
+
export interface HostLanguageOperationOptions {
|
|
7
|
+
readonly workspace: WorkspaceInfo;
|
|
8
|
+
readonly processEnv: NodeJS.ProcessEnv;
|
|
9
|
+
readonly commandRules: readonly CommandRule[];
|
|
10
|
+
readonly overlayAbsolutePath: string;
|
|
11
|
+
readonly signal: AbortSignal;
|
|
12
|
+
readonly limits?: LanguageServiceLimits | undefined;
|
|
13
|
+
readonly now?: (() => number) | undefined;
|
|
14
|
+
readonly spawn?: LspSpawnFn | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function runHostLanguageOperation(request: LanguageServiceRequest, options: HostLanguageOperationOptions): Promise<LanguageServiceOutcome | undefined>;
|
|
17
|
+
//# sourceMappingURL=hostLanguageOperation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostLanguageOperation.d.ts","sourceRoot":"","sources":["../../../src/editor/lsp/hostLanguageOperation.ts"],"names":[],"mappings":"AACA,OAAO,EAWL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAI5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AASpE,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAOvE,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACzC;AA8cD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CA+C7C"}
|