zcode-acp-server 0.47.10 → 0.48.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/README.md +1 -1
- package/dist/code-fingerprint.json +2 -2
- package/dist/config/workflow-gate.d.ts +97 -0
- package/dist/config/workflow-gate.d.ts.map +1 -0
- package/dist/config/workflow-gate.js +159 -0
- package/dist/config/workflow-gate.js.map +1 -0
- package/dist/handlers/background-tasks.d.ts.map +1 -1
- package/dist/handlers/background-tasks.js +56 -0
- package/dist/handlers/background-tasks.js.map +1 -1
- package/dist/handlers/dispatch.js +4 -0
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/session.d.ts +11 -0
- package/dist/handlers/session.d.ts.map +1 -1
- package/dist/handlers/session.js +71 -28
- package/dist/handlers/session.js.map +1 -1
- package/dist/handlers/slash.d.ts +7 -2
- package/dist/handlers/slash.d.ts.map +1 -1
- package/dist/handlers/slash.js +111 -4
- package/dist/handlers/slash.js.map +1 -1
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +6 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/remote/session-close-endpoint.d.ts.map +1 -1
- package/dist/remote/session-close-endpoint.js +6 -0
- package/dist/remote/session-close-endpoint.js.map +1 -1
- package/dist/remote/settings-endpoint.d.ts.map +1 -1
- package/dist/remote/settings-endpoint.js +362 -3
- package/dist/remote/settings-endpoint.js.map +1 -1
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +73 -0
- package/dist/server.js.map +1 -1
- package/dist/settings/workflow.d.ts +139 -0
- package/dist/settings/workflow.d.ts.map +1 -0
- package/dist/settings/workflow.js +443 -0
- package/dist/settings/workflow.js.map +1 -0
- package/dist/translators/tool-helpers.d.ts.map +1 -1
- package/dist/translators/tool-helpers.js +4 -0
- package/dist/translators/tool-helpers.js.map +1 -1
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +8 -0
- package/dist/utils.js.map +1 -1
- package/dist/workflow/poller.d.ts +68 -0
- package/dist/workflow/poller.d.ts.map +1 -0
- package/dist/workflow/poller.js +290 -0
- package/dist/workflow/poller.js.map +1 -0
- package/docs/BACKLOG.md +20 -2
- package/package.json +1 -1
package/dist/handlers/session.js
CHANGED
|
@@ -24,13 +24,14 @@ import { buildProviderRegistry } from "../config/provider-registry.js";
|
|
|
24
24
|
import { pushAccountProviderConfig } from "../config/account-provider.js";
|
|
25
25
|
import { initialSessionMode } from "../config/settings.js";
|
|
26
26
|
import { applyModelSwitch, buildResumeRuntimeModel } from "../config/runtime-model.js";
|
|
27
|
+
import { filterWorkflowCommands, rememberGate, workflowGateNow } from "../config/workflow-gate.js";
|
|
27
28
|
import { messages } from "../i18n.js";
|
|
28
29
|
import { lookupLazySession, lookupModelChoiceByZcodeSid, recordMaterializedSession, rememberLazySession, } from "../lazy-sessions.js";
|
|
29
30
|
import { refreshTerminalTabTitle } from "../terminal-title.js";
|
|
30
31
|
import { buildDiffContent, EventTranslator, extractLocations, formatTurnError, isBackendLostError, isTransientTurnError, ProjectionDiffer, } from "../translators/index.js";
|
|
31
32
|
import { clientConnectionRoot, log, warn } from "../utils.js";
|
|
32
33
|
import { dispatchEvent } from "./dispatch.js";
|
|
33
|
-
import { emitSessionTurnState, sendSessionUpdate, sendTextChunk, withReplayBatch } from "./io.js";
|
|
34
|
+
import { emitSessionTurnState, sendAvailableCommandsDeferred, sendSessionUpdate, sendTextChunk, withReplayBatch, } from "./io.js";
|
|
34
35
|
import { EDIT_DIFF_READ_LIMIT, fetchMessages, fetchMessagesSinceAnchor, fullSlice, readTailLimit, REPLY_READ_LIMIT, replayMessages, sliceTail, TURN_READ, } from "./replay.js";
|
|
35
36
|
import { extractPermDeniedPath, extractSandboxDenial, GENERIC_HINT_KEY, handleSandboxDenial, READ_ONLY_TOOLS, } from "./sandbox-allow.js";
|
|
36
37
|
import { handleServerRequests } from "./server-requests.js";
|
|
@@ -39,6 +40,60 @@ function workspaceFor(cwd) {
|
|
|
39
40
|
const p = cwd || process.cwd();
|
|
40
41
|
return { workspacePath: p, workspaceKey: p };
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Dynamic-workflow session flag (desktop-host parity, server.backendWorkflowGate):
|
|
45
|
+
* `{dynamicWorkflowEnabled:true}` rides every session/create·resume when the
|
|
46
|
+
* gate resolved enabled; otherwise NOTHING is written — the backend's zod
|
|
47
|
+
* schemas are strict, and the flag-absent default is exactly fail-closed.
|
|
48
|
+
* Awaiting the gate on the hot path is free (the verdict resolved at backend
|
|
49
|
+
* spawn); the cold-path bound is the gate fetch's 6s timeout, well under a
|
|
50
|
+
* backend boot. Fork and sub-agent sessions copy the flag inside the backend
|
|
51
|
+
* — no bridge-side injection point for those.
|
|
52
|
+
*/
|
|
53
|
+
async function workflowFlag(server) {
|
|
54
|
+
const promise = server.backendWorkflowGate;
|
|
55
|
+
if (!promise)
|
|
56
|
+
return {};
|
|
57
|
+
const gate = await promise;
|
|
58
|
+
// Belt-and-braces vs captureGate (ensureBackend): an awaited verdict becomes
|
|
59
|
+
// synchronously readable immediately, so the send-time menu filter that runs
|
|
60
|
+
// right after create/resume sees it on its FIRST call.
|
|
61
|
+
rememberGate(promise, gate);
|
|
62
|
+
return gate.enabled ? { dynamicWorkflowEnabled: true } : {};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Cold-bridge menu catch-up: the `/` menu snapshot sent at session/new is
|
|
66
|
+
* filtered against the workflow gate, and on a COLD bridge (lazy session/new,
|
|
67
|
+
* no backend yet) that snapshot was taken while the gate was still
|
|
68
|
+
* pending/absent — the two workflow commands get dropped for that whole
|
|
69
|
+
* session. By the time a lazy session MATERIALIZES the gate has necessarily
|
|
70
|
+
* settled (create/resume params awaited it), so re-send the menu here.
|
|
71
|
+
* Overwrite semantics + the deferred helper's timer cancellation make this
|
|
72
|
+
* idempotent and correctly ordered (a late settle supersedes the stale send).
|
|
73
|
+
*/
|
|
74
|
+
export function resendMenuAfterGateSettled(server, acpSid) {
|
|
75
|
+
if (!server.allCommands || !workflowGateNow(server)?.enabled)
|
|
76
|
+
return;
|
|
77
|
+
for (const sid of server.sessionAliases(acpSid)) {
|
|
78
|
+
sendAvailableCommandsDeferred(server.clients, sid, filterWorkflowCommands(server, server.allCommands));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Shared `session/resume` params builder: {sessionId, workspace} + optional
|
|
83
|
+
* session-lifetime mcpServers + the conditional dynamic-workflow flag. Every
|
|
84
|
+
* resume construction site (session/resume, /resume adoption, session/load,
|
|
85
|
+
* eviction/respawn reload) goes through here so the param shape cannot drift.
|
|
86
|
+
*/
|
|
87
|
+
async function resumeParams(server, zcodeSid, cwd, mcpServers) {
|
|
88
|
+
const params = {
|
|
89
|
+
sessionId: zcodeSid,
|
|
90
|
+
workspace: workspaceFor(cwd),
|
|
91
|
+
...(await workflowFlag(server)),
|
|
92
|
+
};
|
|
93
|
+
if (mcpServers)
|
|
94
|
+
params.mcpServers = mcpServers;
|
|
95
|
+
return params;
|
|
96
|
+
}
|
|
42
97
|
/**
|
|
43
98
|
* A client-supplied cwd is only ever trusted for `session/new` — creating a
|
|
44
99
|
* session is the editor declaring its worktree. "/" is never a project root:
|
|
@@ -510,6 +565,9 @@ export async function ensureRealSession(server, acpSid, opts = {}) {
|
|
|
510
565
|
if (opts.ensureResident !== false) {
|
|
511
566
|
await ensureBackendResident(server, acpSid, record.zcodeSid);
|
|
512
567
|
}
|
|
568
|
+
// Materialization (resume flight) settled the gate — refresh a menu
|
|
569
|
+
// snapshot that may predate the verdict (see resendMenuAfterGateSettled).
|
|
570
|
+
resendMenuAfterGateSettled(server, acpSid);
|
|
513
571
|
return record.zcodeSid;
|
|
514
572
|
}
|
|
515
573
|
if (record) {
|
|
@@ -550,6 +608,9 @@ export async function ensureRealSession(server, acpSid, opts = {}) {
|
|
|
550
608
|
// ZCODE_ACP_MODE; the default stays "yolo" (unrestricted), which is
|
|
551
609
|
// what the bridge always used.
|
|
552
610
|
mode: initialSessionMode(),
|
|
611
|
+
// Dynamic-workflow enable rides create only when the gate says so —
|
|
612
|
+
// workflowFlag writes nothing otherwise (strict zod).
|
|
613
|
+
...(await workflowFlag(server)),
|
|
553
614
|
};
|
|
554
615
|
if (pending.mcpServers && pending.mcpServers.length > 0) {
|
|
555
616
|
createParams.mcpServers = pending.mcpServers;
|
|
@@ -598,6 +659,9 @@ export async function ensureRealSession(server, acpSid, opts = {}) {
|
|
|
598
659
|
recordMaterializedSession(acpSid, sid, pending.cwd);
|
|
599
660
|
log(`session/new ${acpSid} → created ${sid} (lazy, on first use)`);
|
|
600
661
|
await server.ensureBackgroundListener(sid);
|
|
662
|
+
// Cold-bridge catch-up: the session/new menu snapshot was filtered on a
|
|
663
|
+
// pending gate; the create above settled it — re-send for this session.
|
|
664
|
+
resendMenuAfterGateSettled(server, acpSid);
|
|
601
665
|
// Sync to the App's tasks-index.sqlite so the App UI shows this session.
|
|
602
666
|
// Best-effort; failures are logged inside upsertSessionTask and swallowed.
|
|
603
667
|
const { upsertSessionTask } = await import("../tasks-index.js");
|
|
@@ -812,18 +876,12 @@ export async function resumeSession(server, params, cx) {
|
|
|
812
876
|
// the faithful resume fails outright). The params deliberately carry NO
|
|
813
877
|
// apiKey either (the backend's schema rejects it; it resolves auth from
|
|
814
878
|
// its own config/OAuth store).
|
|
815
|
-
const zcParams = {
|
|
816
|
-
sessionId: zcodeSid,
|
|
817
|
-
workspace: workspaceFor(cwd),
|
|
818
|
-
};
|
|
819
879
|
// ACP session/resume may also carry mcpServers; the backend's resume
|
|
820
880
|
// schema accepts the same array shape (verified: an unknown key would be
|
|
821
881
|
// rejected before the session lookup). Remembered as session-lifetime
|
|
822
882
|
// state so later reloads keep re-sending them (#193).
|
|
823
883
|
rememberSessionMcpServers(server, acpSid, params.mcpServers);
|
|
824
|
-
const
|
|
825
|
-
if (storedMcp)
|
|
826
|
-
zcParams.mcpServers = storedMcp;
|
|
884
|
+
const zcParams = await resumeParams(server, zcodeSid, cwd, server.sessionMcpServers.get(acpSid));
|
|
827
885
|
// Push the provider registry BEFORE resume: a resumed session may carry a
|
|
828
886
|
// third-party model in its history, and the backend needs the provider
|
|
829
887
|
// registered to even process the resume turn.
|
|
@@ -955,14 +1013,9 @@ export async function resumeIntoSession(server, cx, acpSid, zcodeTarget) {
|
|
|
955
1013
|
let settledHistory;
|
|
956
1014
|
try {
|
|
957
1015
|
await syncProviderRegistry(server, cwd);
|
|
958
|
-
const outcome = await resumePreservingModel(server,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
// Session-lifetime MCP set keeps riding every resume (#193).
|
|
962
|
-
...(server.sessionMcpServers.get(acpSid)
|
|
963
|
-
? { mcpServers: server.sessionMcpServers.get(acpSid) }
|
|
964
|
-
: {}),
|
|
965
|
-
});
|
|
1016
|
+
const outcome = await resumePreservingModel(server,
|
|
1017
|
+
// Session-lifetime MCP set keeps riding every resume (#193).
|
|
1018
|
+
await resumeParams(server, zcodeTarget, cwd, server.sessionMcpServers.get(acpSid)));
|
|
966
1019
|
settledHistory = outcome.history;
|
|
967
1020
|
server.markBackendLoaded(acpSid);
|
|
968
1021
|
await repairUnavailableModel(server, zcodeTarget);
|
|
@@ -1032,16 +1085,10 @@ export async function loadSession(server, params, cx, opts = {}) {
|
|
|
1032
1085
|
// snapshot is the mid-hydration-prefix guard; undefined = alreadyLive).
|
|
1033
1086
|
let settledHistory;
|
|
1034
1087
|
if (!alreadyLive) {
|
|
1035
|
-
const zcParams = {
|
|
1036
|
-
sessionId: zcodeSid,
|
|
1037
|
-
workspace: workspaceFor(cwd),
|
|
1038
|
-
};
|
|
1039
1088
|
// Same mcpServers contract as resume: the client may re-declare them on
|
|
1040
1089
|
// load; a stored set keeps riding along when it doesn't (#193).
|
|
1041
1090
|
rememberSessionMcpServers(server, acpSid, params.mcpServers);
|
|
1042
|
-
const
|
|
1043
|
-
if (storedMcp)
|
|
1044
|
-
zcParams.mcpServers = storedMcp;
|
|
1091
|
+
const zcParams = await resumeParams(server, zcodeSid, cwd, server.sessionMcpServers.get(acpSid));
|
|
1045
1092
|
// Push the provider registry BEFORE resume: a loaded session may carry a
|
|
1046
1093
|
// third-party model in its history, and the backend needs the provider
|
|
1047
1094
|
// registered to process it.
|
|
@@ -2458,17 +2505,13 @@ async function resumeBackendSession(server, zcParams) {
|
|
|
2458
2505
|
*/
|
|
2459
2506
|
export async function reloadBackendSession(server, acpSid, zcodeSid) {
|
|
2460
2507
|
const cwd = server.sessionCwds.get(acpSid) ?? process.cwd();
|
|
2461
|
-
const zcParams = {
|
|
2462
|
-
sessionId: zcodeSid,
|
|
2463
|
-
workspace: workspaceFor(cwd),
|
|
2464
|
-
};
|
|
2465
2508
|
// Eviction/respawn reload: the backend lost the per-load mcpServers runtime
|
|
2466
2509
|
// config with its resident session — re-send or the tools vanish (#193).
|
|
2467
2510
|
const reloadMcp = server.sessionMcpServers.get(acpSid);
|
|
2468
2511
|
if (reloadMcp) {
|
|
2469
|
-
zcParams.mcpServers = reloadMcp;
|
|
2470
2512
|
log(`session/resume (reload) carrying ${reloadMcp.length} client MCP server(s)`);
|
|
2471
2513
|
}
|
|
2514
|
+
const zcParams = await resumeParams(server, zcodeSid, cwd, reloadMcp);
|
|
2472
2515
|
// Same pre-resume steps as the ACP resume/load handlers: register the
|
|
2473
2516
|
// provider registry (a resumed session's history references a model the
|
|
2474
2517
|
// fresh backend can't process until its provider is registered — sends
|