@astrosheep/keiyaku 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -5
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +23 -14
- package/build/src/akuma/akuma.js +120 -104
- package/build/src/akuma/archetype.d.ts +7 -6
- package/build/src/akuma/archetype.js +68 -41
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +408 -213
- package/build/src/akuma/heart/facts.d.ts +25 -34
- package/build/src/akuma/heart/facts.js +18 -7
- package/build/src/akuma/heart/index.d.ts +39 -42
- package/build/src/akuma/heart/index.js +134 -93
- package/build/src/akuma/heart/rows.d.ts +9 -31
- package/build/src/akuma/heart/rows.js +26 -44
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +7 -15
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +28 -14
- package/build/src/akuma/heart/storage.js +86 -26
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +6 -1
- package/build/src/akuma/index.js +6 -0
- package/build/src/akuma/projection.d.ts +117 -38
- package/build/src/akuma/projection.js +208 -97
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +12 -4
- package/build/src/akuma/provider.js +117 -70
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/claude/events.js +66 -13
- package/build/src/akuma/providers/claude/index.d.ts +2 -1
- package/build/src/akuma/providers/claude/index.js +35 -21
- package/build/src/akuma/providers/codex-app-server/events.js +4 -1
- package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
- package/build/src/akuma/providers/codex-app-server/index.js +24 -31
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +24 -59
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
- package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
- package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
- package/build/src/akuma/providers/pi/events.js +49 -11
- package/build/src/akuma/providers/pi/index.d.ts +1 -1
- package/build/src/akuma/providers/pi/index.js +50 -21
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -7
- package/build/src/akuma/requests.js +92 -65
- package/build/src/alias/index.d.ts +2 -2
- package/build/src/alias/index.js +10 -10
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/cli/accepted.d.ts +17 -13
- package/build/src/cli/accepted.js +72 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
- package/build/src/cli/commands/akuma-invoke.js +14 -9
- package/build/src/cli/commands/akuma.d.ts +13 -3
- package/build/src/cli/commands/akuma.js +67 -34
- package/build/src/cli/commands/contract.d.ts +18 -5
- package/build/src/cli/commands/contract.js +9 -3
- package/build/src/cli/commands/task-invoke.d.ts +17 -4
- package/build/src/cli/commands/task-invoke.js +43 -20
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +51 -13
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +4 -3
- package/build/src/cli/invoke.js +160 -179
- package/build/src/cli/main.js +34 -6
- package/build/src/cli/parse.d.ts +9 -2
- package/build/src/cli/parse.js +29 -7
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +2 -2
- package/build/src/cli/render/akuma-tool.js +30 -4
- package/build/src/cli/render/akuma.d.ts +1 -0
- package/build/src/cli/render/akuma.js +211 -116
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +192 -79
- package/build/src/cli/render/kanshi.js +263 -168
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -20
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +7 -0
- package/build/src/cli/render/terminal.js +46 -0
- package/build/src/cli/render/text.js +6 -3
- package/build/src/cli/result.d.ts +115 -20
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +6 -5
- package/build/src/contract-worktree.js +95 -57
- package/build/src/coordination/durable-file.d.ts +4 -3
- package/build/src/coordination/durable-file.js +29 -25
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +0 -11
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +1 -7
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +4 -1
- package/build/src/dispatch/index.d.ts +2 -2
- package/build/src/dispatch/index.js +17 -17
- package/build/src/git/admission.d.ts +1 -1
- package/build/src/git/admission.js +11 -11
- package/build/src/git/hooks.js +13 -13
- package/build/src/git/integration.d.ts +14 -15
- package/build/src/git/integration.js +80 -44
- package/build/src/git/observe.d.ts +5 -3
- package/build/src/git/observe.js +18 -12
- package/build/src/git/read-observation.js +9 -9
- package/build/src/git/reconcile.d.ts +2 -0
- package/build/src/git/reconcile.js +138 -95
- package/build/src/git/repository.d.ts +27 -19
- package/build/src/git/repository.js +155 -83
- package/build/src/git/scratch.d.ts +7 -3
- package/build/src/git/scratch.js +44 -37
- package/build/src/git/target-placement.d.ts +40 -4
- package/build/src/git/target-placement.js +221 -101
- package/build/src/git/tender.d.ts +6 -5
- package/build/src/git/tender.js +24 -43
- package/build/src/git/terminal-seal.d.ts +1 -1
- package/build/src/git/terminal-seal.js +7 -5
- package/build/src/git/workspace.d.ts +36 -4
- package/build/src/git/workspace.js +53 -18
- package/build/src/identity/selector.js +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.js +1 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +2 -1
- package/build/src/kanshi/read.js +118 -27
- package/build/src/kanshi/report.d.ts +50 -0
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +32 -0
- package/build/src/library/address.d.ts +12 -3
- package/build/src/library/address.js +77 -46
- package/build/src/library/akuma-creation.d.ts +6 -1
- package/build/src/library/akuma-creation.js +86 -13
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.js +4 -4
- package/build/src/library/catalog.d.ts +1 -1
- package/build/src/library/catalog.js +5 -8
- package/build/src/library/contract.d.ts +5 -6
- package/build/src/library/contract.js +11 -35
- package/build/src/library/delivery.d.ts +2 -1
- package/build/src/library/fleet.d.ts +2 -2
- package/build/src/library/fleet.js +35 -71
- package/build/src/library/keiyaku.d.ts +3 -2
- package/build/src/library/keiyaku.js +1 -0
- package/build/src/library/mutation.d.ts +7 -6
- package/build/src/library/mutation.js +23 -25
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/region.d.ts +12 -0
- package/build/src/library/region.js +15 -1
- package/build/src/library/repo.d.ts +4 -16
- package/build/src/library/repo.js +13 -38
- package/build/src/protocol/attempt.js +6 -6
- package/build/src/protocol/bind.js +4 -4
- package/build/src/protocol/intent.d.ts +15 -2
- package/build/src/protocol/intent.js +21 -6
- package/build/src/protocol/operations.d.ts +73 -22
- package/build/src/protocol/operations.js +292 -88
- package/build/src/protocol/outcome.d.ts +9 -3
- package/build/src/protocol/outcome.js +3 -1
- package/build/src/protocol/placement.d.ts +15 -4
- package/build/src/protocol/placement.js +58 -25
- package/build/src/protocol/read/status.d.ts +8 -1
- package/build/src/protocol/read/status.js +61 -14
- package/build/src/runtime/proc/line-rpc.d.ts +3 -8
- package/build/src/runtime/proc/line-rpc.js +16 -43
- package/build/src/runtime/proc/run.d.ts +15 -29
- package/build/src/runtime/proc/run.js +113 -105
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +2 -2
- package/build/src/settings.js +8 -8
- package/build/src/settlement/fence.d.ts +0 -4
- package/build/src/settlement/fence.js +0 -9
- package/build/src/settlement/holder.d.ts +11 -1
- package/build/src/settlement/holder.js +25 -0
- package/build/src/settlement/settle.d.ts +1 -1
- package/build/src/settlement/settle.js +52 -89
- package/build/src/task/board.d.ts +3 -3
- package/build/src/task/board.js +6 -9
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +14 -14
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +19 -7
- package/build/src/task/index.js +42 -23
- package/build/src/task/operations.d.ts +15 -9
- package/build/src/task/operations.js +78 -48
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +2 -2
- package/build/src/task/store.js +40 -25
- package/build/src/verification/execution.d.ts +4 -2
- package/build/src/verification/execution.js +9 -5
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +13 -2
- package/build/src/world.js +81 -48
- package/package.json +3 -2
- package/build/src/protocol/read/audit.d.ts +0 -31
- package/build/src/protocol/read/audit.js +0 -55
|
@@ -16,82 +16,110 @@ function object(value) {
|
|
|
16
16
|
: null;
|
|
17
17
|
}
|
|
18
18
|
export { decodeResumeCoordinate, encodeResumeCoordinate };
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
function eventType(value) {
|
|
20
|
+
return typeof value === "string" && Object.hasOwn(AGENT_EVENT_TYPES, value);
|
|
21
|
+
}
|
|
22
|
+
const SEARCH_SCOPES = {
|
|
23
|
+
content: true,
|
|
24
|
+
files: true,
|
|
25
|
+
web: true,
|
|
26
|
+
};
|
|
27
|
+
function decodePositiveLine(value) {
|
|
28
|
+
if (value === undefined)
|
|
22
29
|
return undefined;
|
|
23
|
-
|
|
24
|
-
throw new TypeError(`provider option ${field} must be ${blank === "allow" ? "a string" : "a nonblank string"}`);
|
|
25
|
-
}
|
|
26
|
-
return selected;
|
|
30
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : null;
|
|
27
31
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
if (selected === undefined)
|
|
32
|
+
function decodeOptionalText(value) {
|
|
33
|
+
if (value === undefined)
|
|
31
34
|
return undefined;
|
|
32
|
-
|
|
33
|
-
throw new TypeError(`provider option ${field} must be ${allowed.join(", ")}`);
|
|
34
|
-
}
|
|
35
|
-
return selected;
|
|
36
|
-
}
|
|
37
|
-
export function decodeProviderOptions(value) {
|
|
38
|
-
const options = object(value);
|
|
39
|
-
if (options === null)
|
|
40
|
-
throw new TypeError("provider options must be an object");
|
|
41
|
-
const allowed = ["access", "effort", "model", "network", "systemPrompt"];
|
|
42
|
-
const unknown = Object.keys(options).find((key) => !allowed.includes(key));
|
|
43
|
-
if (unknown !== undefined)
|
|
44
|
-
throw new TypeError(`provider options have unknown field ${unknown}`);
|
|
45
|
-
const model = optionText(options, "model", "refuse");
|
|
46
|
-
const effort = optionText(options, "effort", "refuse");
|
|
47
|
-
const access = optionEnum(options, "access", ["read", "write", "auto"]);
|
|
48
|
-
const network = optionEnum(options, "network", ["disabled", "enabled"]);
|
|
49
|
-
const systemPrompt = optionText(options, "systemPrompt", "allow");
|
|
50
|
-
return Object.freeze({
|
|
51
|
-
...(model === undefined ? {} : { model }),
|
|
52
|
-
...(effort === undefined ? {} : { effort }),
|
|
53
|
-
...(access === undefined ? {} : { access }),
|
|
54
|
-
...(network === undefined ? {} : { network }),
|
|
55
|
-
...(systemPrompt === undefined ? {} : { systemPrompt }),
|
|
56
|
-
});
|
|
35
|
+
return typeof value === "string" ? value : null;
|
|
57
36
|
}
|
|
58
|
-
function
|
|
59
|
-
|
|
37
|
+
function decodeSearchScope(value) {
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
return typeof value === "string" && Object.hasOwn(SEARCH_SCOPES, value) ? value : null;
|
|
41
|
+
}
|
|
42
|
+
function decodeRunCall(call) {
|
|
43
|
+
return typeof call.command === "string" ? { kind: "run", command: call.command } : null;
|
|
44
|
+
}
|
|
45
|
+
function decodeReadCall(call) {
|
|
46
|
+
if (typeof call.path !== "string")
|
|
47
|
+
return null;
|
|
48
|
+
const offset = decodePositiveLine(call.offset);
|
|
49
|
+
const limit = decodePositiveLine(call.limit);
|
|
50
|
+
if (offset === null || limit === null)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
kind: "read",
|
|
54
|
+
path: call.path,
|
|
55
|
+
...(offset === undefined ? {} : { offset }),
|
|
56
|
+
...(limit === undefined ? {} : { limit }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function decodeSearchCall(call) {
|
|
60
|
+
if (typeof call.query !== "string")
|
|
61
|
+
return null;
|
|
62
|
+
const scope = decodeSearchScope(call.scope);
|
|
63
|
+
const path = decodeOptionalText(call.path);
|
|
64
|
+
const glob = decodeOptionalText(call.glob);
|
|
65
|
+
if (scope === null || path === null || glob === null)
|
|
66
|
+
return null;
|
|
67
|
+
return {
|
|
68
|
+
kind: "search",
|
|
69
|
+
query: call.query,
|
|
70
|
+
...(scope === undefined ? {} : { scope }),
|
|
71
|
+
...(path === undefined ? {} : { path }),
|
|
72
|
+
...(glob === undefined ? {} : { glob }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function decodeNonnegativeCount(value) {
|
|
76
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
77
|
+
}
|
|
78
|
+
function decodeDiffstat(value) {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
const diffstat = object(value);
|
|
82
|
+
if (diffstat === null)
|
|
83
|
+
return null;
|
|
84
|
+
const added = decodeNonnegativeCount(diffstat.added);
|
|
85
|
+
const removed = decodeNonnegativeCount(diffstat.removed);
|
|
86
|
+
return added === null || removed === null ? null : { added, removed };
|
|
87
|
+
}
|
|
88
|
+
function decodeFileChangeMember(value) {
|
|
89
|
+
const change = object(value);
|
|
90
|
+
if (change === null || (change.op !== "add" && change.op !== "update" && change.op !== "delete")
|
|
91
|
+
|| typeof change.path !== "string")
|
|
92
|
+
return null;
|
|
93
|
+
const diffstat = decodeDiffstat(change.diffstat);
|
|
94
|
+
if (diffstat === null)
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
op: change.op,
|
|
98
|
+
path: change.path,
|
|
99
|
+
...(diffstat === undefined ? {} : { diffstat }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function decodeFileChangeCall(call) {
|
|
103
|
+
if (!Array.isArray(call.changes))
|
|
104
|
+
return null;
|
|
105
|
+
const changes = call.changes.map(decodeFileChangeMember);
|
|
106
|
+
return changes.every((change) => change !== null) ? { kind: "fileChange", changes } : null;
|
|
107
|
+
}
|
|
108
|
+
function decodeOtherCall(call) {
|
|
109
|
+
return typeof call.display === "string" ? { kind: "other", display: call.display } : null;
|
|
60
110
|
}
|
|
61
111
|
function decodeToolCall(value) {
|
|
62
112
|
const call = object(value);
|
|
63
|
-
if (call
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (change === null || (change.op !== "add" && change.op !== "update" && change.op !== "delete")
|
|
73
|
-
|| typeof change.path !== "string")
|
|
74
|
-
return null;
|
|
75
|
-
const rawDiffstat = change.diffstat;
|
|
76
|
-
const op = change.op;
|
|
77
|
-
if (rawDiffstat === undefined)
|
|
78
|
-
return { op, path: change.path };
|
|
79
|
-
const diffstat = object(rawDiffstat);
|
|
80
|
-
if (diffstat === null || !Number.isSafeInteger(diffstat.added) || !Number.isSafeInteger(diffstat.removed)
|
|
81
|
-
|| diffstat.added < 0 || diffstat.removed < 0)
|
|
82
|
-
return null;
|
|
83
|
-
return {
|
|
84
|
-
op,
|
|
85
|
-
path: change.path,
|
|
86
|
-
diffstat: { added: diffstat.added, removed: diffstat.removed },
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
if (changes.every((change) => change !== null))
|
|
90
|
-
return { kind: "fileChange", changes };
|
|
113
|
+
if (call === null)
|
|
114
|
+
return null;
|
|
115
|
+
switch (call.kind) {
|
|
116
|
+
case "run": return decodeRunCall(call);
|
|
117
|
+
case "read": return decodeReadCall(call);
|
|
118
|
+
case "search": return decodeSearchCall(call);
|
|
119
|
+
case "fileChange": return decodeFileChangeCall(call);
|
|
120
|
+
case "other": return decodeOtherCall(call);
|
|
121
|
+
default: return null;
|
|
91
122
|
}
|
|
92
|
-
if (call?.kind === "other" && typeof call.display === "string")
|
|
93
|
-
return { kind: "other", display: call.display };
|
|
94
|
-
return null;
|
|
95
123
|
}
|
|
96
124
|
function decodeToolResult(value) {
|
|
97
125
|
const result = object(value);
|
|
@@ -153,8 +181,27 @@ export function decodeAgentEvent(value) {
|
|
|
153
181
|
function boundedToolCall(call) {
|
|
154
182
|
switch (call.kind) {
|
|
155
183
|
case "run": return { value: { kind: call.kind, command: boundedEventText(call.command) }, truncated: call.command.length > AGENT_EVENT_TEXT_LIMIT };
|
|
156
|
-
case "read": return {
|
|
157
|
-
|
|
184
|
+
case "read": return {
|
|
185
|
+
value: {
|
|
186
|
+
kind: call.kind,
|
|
187
|
+
path: boundedEventText(call.path),
|
|
188
|
+
...(call.offset === undefined ? {} : { offset: call.offset }),
|
|
189
|
+
...(call.limit === undefined ? {} : { limit: call.limit }),
|
|
190
|
+
},
|
|
191
|
+
truncated: call.path.length > AGENT_EVENT_TEXT_LIMIT,
|
|
192
|
+
};
|
|
193
|
+
case "search": return {
|
|
194
|
+
value: {
|
|
195
|
+
kind: call.kind,
|
|
196
|
+
query: boundedEventText(call.query),
|
|
197
|
+
...(call.scope === undefined ? {} : { scope: call.scope }),
|
|
198
|
+
...(call.path === undefined ? {} : { path: boundedEventText(call.path) }),
|
|
199
|
+
...(call.glob === undefined ? {} : { glob: boundedEventText(call.glob) }),
|
|
200
|
+
},
|
|
201
|
+
truncated: call.query.length > AGENT_EVENT_TEXT_LIMIT
|
|
202
|
+
|| (call.path !== undefined && call.path.length > AGENT_EVENT_TEXT_LIMIT)
|
|
203
|
+
|| (call.glob !== undefined && call.glob.length > AGENT_EVENT_TEXT_LIMIT),
|
|
204
|
+
};
|
|
158
205
|
case "fileChange": return {
|
|
159
206
|
value: {
|
|
160
207
|
kind: call.kind,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
2
|
+
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
3
|
+
import { type ProviderAdapter, type Session } from "../../provider.js";
|
|
4
|
+
export type AcpStartInput = Parameters<ProviderAdapter["start"]>[0] | Parameters<NonNullable<ProviderAdapter["resume"]>>[0];
|
|
5
|
+
export type AcpDependencies = Readonly<{
|
|
6
|
+
spawnProcess?: typeof spawnStdioProcess;
|
|
7
|
+
}>;
|
|
8
|
+
export type AcpLiveSession = Readonly<{
|
|
9
|
+
session: Session;
|
|
10
|
+
agent: acp.ClientContext;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
open(): boolean;
|
|
13
|
+
}>;
|
|
14
|
+
type AcpLaunch = Readonly<{
|
|
15
|
+
argv: readonly [string, ...string[]];
|
|
16
|
+
env?: Readonly<Record<string, string>>;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function startAcpSession(launch: AcpLaunch, input: AcpStartInput, dependencies?: AcpDependencies): Promise<AcpLiveSession>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
2
|
+
import { Readable, Writable } from "node:stream";
|
|
3
|
+
import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
|
|
4
|
+
import { abortable } from "../../abort.js";
|
|
5
|
+
import { AgentEventChannel, } from "../../provider.js";
|
|
6
|
+
import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate } from "./events.js";
|
|
7
|
+
function diagnostic(error) {
|
|
8
|
+
if (!(error instanceof Error))
|
|
9
|
+
return String(error);
|
|
10
|
+
if (!(error instanceof acp.RequestError) || error.data === undefined)
|
|
11
|
+
return error.message;
|
|
12
|
+
let data;
|
|
13
|
+
try {
|
|
14
|
+
data = typeof error.data === "string" ? error.data : JSON.stringify(error.data);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
data = String(error.data);
|
|
18
|
+
}
|
|
19
|
+
return data.length === 0 ? `${error.message} [${error.code}]` : `${error.message} [${error.code}]: ${data}`;
|
|
20
|
+
}
|
|
21
|
+
function createAcpClient(onUpdate) {
|
|
22
|
+
return acp.client({ name: "keiyaku" })
|
|
23
|
+
.onNotification(acp.methods.client.session.update, ({ params }) => onUpdate(params));
|
|
24
|
+
}
|
|
25
|
+
function promptResult(response) {
|
|
26
|
+
if (response.stopReason === "end_turn" || response.stopReason === "max_tokens"
|
|
27
|
+
|| response.stopReason === "max_turn_requests") {
|
|
28
|
+
return { kind: "answered", answer: "" };
|
|
29
|
+
}
|
|
30
|
+
return { kind: "failed", diagnostic: `ACP prompt ended ${response.stopReason}` };
|
|
31
|
+
}
|
|
32
|
+
async function establishSession(agent, input) {
|
|
33
|
+
const initialized = await agent.request(acp.methods.agent.initialize, {
|
|
34
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
35
|
+
clientInfo: { name: "keiyaku", version: "4" },
|
|
36
|
+
});
|
|
37
|
+
if (input.session.kind === "resume" && initialized.agentCapabilities?.loadSession !== true) {
|
|
38
|
+
throw new Error("ACP agent does not advertise session/load");
|
|
39
|
+
}
|
|
40
|
+
if (input.session.kind === "fresh") {
|
|
41
|
+
return (await agent.request(acp.methods.agent.session.new, { cwd: input.cwd, mcpServers: [] })).sessionId;
|
|
42
|
+
}
|
|
43
|
+
const sessionId = input.session.coordinate.sessionId;
|
|
44
|
+
if (sessionId === undefined)
|
|
45
|
+
throw new Error("ACP resume coordinate has no session id");
|
|
46
|
+
await agent.request(acp.methods.agent.session.load, { cwd: input.cwd, mcpServers: [], sessionId });
|
|
47
|
+
return sessionId;
|
|
48
|
+
}
|
|
49
|
+
function createAcpTurn(connection) {
|
|
50
|
+
const events = new AgentEventChannel();
|
|
51
|
+
let state = EMPTY_ACP_EVENT_STATE;
|
|
52
|
+
let terminal = false;
|
|
53
|
+
let resolveCompletion;
|
|
54
|
+
const completion = new Promise((resolve) => { resolveCompletion = resolve; });
|
|
55
|
+
const update = (next) => {
|
|
56
|
+
if (terminal)
|
|
57
|
+
return;
|
|
58
|
+
const mapped = mapAcpUpdate(next, state);
|
|
59
|
+
state = mapped.state;
|
|
60
|
+
for (const event of mapped.events)
|
|
61
|
+
events.emit(event);
|
|
62
|
+
};
|
|
63
|
+
const finish = async (result, cleanup) => {
|
|
64
|
+
if (terminal)
|
|
65
|
+
return;
|
|
66
|
+
terminal = true;
|
|
67
|
+
let completionResult = result;
|
|
68
|
+
const failCleanup = (error) => {
|
|
69
|
+
completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(error)}` };
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
await cleanup();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
failCleanup(error);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
connection.close();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
failCleanup(error);
|
|
82
|
+
}
|
|
83
|
+
const flushed = flushAcpEvents(state);
|
|
84
|
+
for (const event of flushed.events)
|
|
85
|
+
events.emit(event);
|
|
86
|
+
events.end();
|
|
87
|
+
resolveCompletion(completionResult.kind === "answered"
|
|
88
|
+
? { ...completionResult, answer: flushed.state.answer }
|
|
89
|
+
: completionResult);
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
events,
|
|
93
|
+
completion,
|
|
94
|
+
update,
|
|
95
|
+
finish,
|
|
96
|
+
open: () => !terminal,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function beginAcpPrompt(agent, child, turn, sessionId, input) {
|
|
100
|
+
void agent.request(acp.methods.agent.session.prompt, {
|
|
101
|
+
sessionId,
|
|
102
|
+
prompt: [{ type: "text", text: input.body }, ...input.launchTells.map(({ text }) => ({ type: "text", text }))],
|
|
103
|
+
}).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(error) }, () => child.endInputAndDrain()));
|
|
104
|
+
void child.exited.then((exit) => turn.finish({
|
|
105
|
+
kind: "failed",
|
|
106
|
+
diagnostic: exit.stderr || `ACP process exited${exit.code === null ? "" : ` with code ${exit.code}`}`,
|
|
107
|
+
}, () => child.endInputAndDrain()));
|
|
108
|
+
return {
|
|
109
|
+
admission: { fence: sessionId },
|
|
110
|
+
events: turn.events,
|
|
111
|
+
completion: turn.completion,
|
|
112
|
+
abort: async () => {
|
|
113
|
+
let result = { kind: "failed", diagnostic: "ACP turn cancelled" };
|
|
114
|
+
try {
|
|
115
|
+
await agent.notify(acp.methods.agent.session.cancel, { sessionId });
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
result = { kind: "failed", diagnostic: diagnostic(error) };
|
|
119
|
+
}
|
|
120
|
+
if (!turn.open())
|
|
121
|
+
await child.close(true);
|
|
122
|
+
else
|
|
123
|
+
await turn.finish(result, () => child.close(true));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export async function startAcpSession(launch, input, dependencies = {}) {
|
|
128
|
+
const signal = input.signal ?? new AbortController().signal;
|
|
129
|
+
signal.throwIfAborted();
|
|
130
|
+
const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
|
|
131
|
+
argv: launch.argv,
|
|
132
|
+
cwd: input.cwd,
|
|
133
|
+
env: { ...globalThis.process.env, ...launch.env },
|
|
134
|
+
});
|
|
135
|
+
let sessionId;
|
|
136
|
+
let turn;
|
|
137
|
+
const connection = createAcpClient((notification) => {
|
|
138
|
+
if (notification.sessionId === sessionId)
|
|
139
|
+
turn.update(notification.update);
|
|
140
|
+
}).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
|
|
141
|
+
turn = createAcpTurn(connection);
|
|
142
|
+
try {
|
|
143
|
+
sessionId = await abortable(establishSession(connection.agent, input), signal);
|
|
144
|
+
turn.events.emit({ type: "session", coordinate: { sessionId } });
|
|
145
|
+
const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
|
|
146
|
+
return { session, agent: connection.agent, sessionId, open: turn.open };
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
connection.close(error);
|
|
150
|
+
try {
|
|
151
|
+
await child.close(true);
|
|
152
|
+
}
|
|
153
|
+
catch (cleanup) {
|
|
154
|
+
throw new Error(`${diagnostic(error)}; ACP cleanup failed: ${diagnostic(cleanup)}`, { cause: error });
|
|
155
|
+
}
|
|
156
|
+
turn.events.end();
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SessionUpdate } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { type AgentEvent } from "../../provider.js";
|
|
3
|
+
type OpenBlock = Readonly<{
|
|
4
|
+
type: "assistant" | "thought";
|
|
5
|
+
text: string;
|
|
6
|
+
}> | null;
|
|
7
|
+
export type AcpEventState = Readonly<{
|
|
8
|
+
answer: string;
|
|
9
|
+
assistantMessageId: string | null;
|
|
10
|
+
open: OpenBlock;
|
|
11
|
+
tools: ReadonlyMap<string, string>;
|
|
12
|
+
}>;
|
|
13
|
+
type AcpEventMapping = Readonly<{
|
|
14
|
+
events: readonly AgentEvent[];
|
|
15
|
+
state: AcpEventState;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const EMPTY_ACP_EVENT_STATE: AcpEventState;
|
|
18
|
+
export declare function flushAcpEvents(state: AcpEventState, boundary?: readonly AgentEvent[]): AcpEventMapping;
|
|
19
|
+
export declare function mapAcpUpdate(update: SessionUpdate, previous: AcpEventState): AcpEventMapping;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { noteEvent, unknownEvent } from "../../provider.js";
|
|
2
|
+
export const EMPTY_ACP_EVENT_STATE = Object.freeze({
|
|
3
|
+
answer: "",
|
|
4
|
+
assistantMessageId: null,
|
|
5
|
+
open: null,
|
|
6
|
+
tools: new Map(),
|
|
7
|
+
});
|
|
8
|
+
export function flushAcpEvents(state, boundary = []) {
|
|
9
|
+
const events = state.open === null ? boundary : [{ type: state.open.type, text: state.open.text }, ...boundary];
|
|
10
|
+
return {
|
|
11
|
+
events,
|
|
12
|
+
state: state.open === null ? state : { ...state, open: null },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function appendBlock(state, type, text, messageId) {
|
|
16
|
+
const newAssistantMessage = type === "assistant"
|
|
17
|
+
&& messageId !== undefined
|
|
18
|
+
&& state.assistantMessageId !== messageId;
|
|
19
|
+
const flushed = state.open === null || (state.open.type === type && !newAssistantMessage)
|
|
20
|
+
? { events: [], state }
|
|
21
|
+
: flushAcpEvents(state);
|
|
22
|
+
return {
|
|
23
|
+
events: flushed.events,
|
|
24
|
+
state: {
|
|
25
|
+
...flushed.state,
|
|
26
|
+
answer: type === "assistant"
|
|
27
|
+
? `${newAssistantMessage ? "" : flushed.state.answer}${text}`
|
|
28
|
+
: flushed.state.answer,
|
|
29
|
+
assistantMessageId: type === "assistant" && messageId !== undefined
|
|
30
|
+
? messageId
|
|
31
|
+
: flushed.state.assistantMessageId,
|
|
32
|
+
open: { type, text: `${flushed.state.open?.text ?? ""}${text}` },
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function mapAcpUpdate(update, previous) {
|
|
37
|
+
switch (update.sessionUpdate) {
|
|
38
|
+
case "agent_message_chunk":
|
|
39
|
+
case "agent_thought_chunk": {
|
|
40
|
+
const text = update.content.type === "text" ? update.content.text : undefined;
|
|
41
|
+
if (text === undefined)
|
|
42
|
+
return flushAcpEvents(previous, [unknownEvent(`${update.sessionUpdate}/${update.content.type}`)]);
|
|
43
|
+
return appendBlock(previous, update.sessionUpdate === "agent_message_chunk" ? "assistant" : "thought", text, update.sessionUpdate === "agent_message_chunk" && update.messageId !== null
|
|
44
|
+
? update.messageId
|
|
45
|
+
: undefined);
|
|
46
|
+
}
|
|
47
|
+
case "user_message_chunk": return flushAcpEvents(previous);
|
|
48
|
+
case "tool_call":
|
|
49
|
+
case "tool_call_update": {
|
|
50
|
+
const name = update.title ?? (update.sessionUpdate === "tool_call" ? undefined : previous.tools.get(update.toolCallId)) ?? "ACP tool";
|
|
51
|
+
const observed = { name, call: { kind: "other", display: name } };
|
|
52
|
+
const tools = new Map(previous.tools);
|
|
53
|
+
if (update.status === "completed" || update.status === "failed") {
|
|
54
|
+
tools.delete(update.toolCallId);
|
|
55
|
+
return flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "completed", id: update.toolCallId, ...observed, result: { status: update.status === "completed" ? "ok" : "error" } }]);
|
|
56
|
+
}
|
|
57
|
+
const started = tools.has(update.toolCallId);
|
|
58
|
+
tools.set(update.toolCallId, name);
|
|
59
|
+
return started
|
|
60
|
+
? flushAcpEvents({ ...previous, tools })
|
|
61
|
+
: flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "started", id: update.toolCallId, ...observed }]);
|
|
62
|
+
}
|
|
63
|
+
case "plan": return flushAcpEvents(previous, [noteEvent(`Plan updated: ${update.entries.map((entry) => entry.content).join("; ")}`)]);
|
|
64
|
+
case "available_commands_update": return flushAcpEvents(previous, [noteEvent("ACP commands updated")]);
|
|
65
|
+
case "current_mode_update": return flushAcpEvents(previous, [noteEvent("ACP mode updated")]);
|
|
66
|
+
case "config_option_update": return flushAcpEvents(previous, [noteEvent("ACP configuration updated")]);
|
|
67
|
+
case "session_info_update": return flushAcpEvents(previous, [noteEvent("ACP session metadata updated")]);
|
|
68
|
+
case "usage_update": return flushAcpEvents(previous);
|
|
69
|
+
case "plan_update":
|
|
70
|
+
case "plan_removed": return flushAcpEvents(previous, [unknownEvent(update.sessionUpdate)]);
|
|
71
|
+
default: return { events: [unknownEvent(String(update.sessionUpdate))], state: previous };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProviderAdapter } from "../../provider.js";
|
|
2
|
+
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
3
|
+
import { type AcpDependencies } from "./core.js";
|
|
4
|
+
export type AcpExecutionConfig = Readonly<{
|
|
5
|
+
argvBefore: readonly string[];
|
|
6
|
+
argvAfter: readonly string[];
|
|
7
|
+
modelArg?: string;
|
|
8
|
+
effortArg?: string;
|
|
9
|
+
systemPromptArg?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
|
|
12
|
+
export declare function createAcpProvider(execution: ProviderExecution, dependencies?: AcpDependencies): ProviderAdapter;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { startAcpSession } from "./core.js";
|
|
2
|
+
function argumentName(value, key) {
|
|
3
|
+
const selected = value[key];
|
|
4
|
+
if (selected === undefined)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (typeof selected !== "string" || selected.trim().length === 0) {
|
|
7
|
+
throw new TypeError(`ACP provider config ${key} must be a nonblank string`);
|
|
8
|
+
}
|
|
9
|
+
return selected;
|
|
10
|
+
}
|
|
11
|
+
export function decodeAcpConfig(value) {
|
|
12
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
13
|
+
throw new TypeError("ACP provider config must be an object");
|
|
14
|
+
}
|
|
15
|
+
const config = value;
|
|
16
|
+
const unknown = Object.keys(config)
|
|
17
|
+
.find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
|
|
18
|
+
if (unknown !== undefined)
|
|
19
|
+
throw new TypeError(`ACP provider config has unknown field ${unknown}`);
|
|
20
|
+
if (!Array.isArray(config.argvBefore)
|
|
21
|
+
|| config.argvBefore.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
|
|
22
|
+
throw new TypeError("ACP provider config argvBefore must be an array of nonblank strings");
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(config.argvAfter)
|
|
25
|
+
|| config.argvAfter.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
|
|
26
|
+
throw new TypeError("ACP provider config argvAfter must be an array of nonblank strings");
|
|
27
|
+
}
|
|
28
|
+
const modelArg = argumentName(config, "modelArg");
|
|
29
|
+
const effortArg = argumentName(config, "effortArg");
|
|
30
|
+
const systemPromptArg = argumentName(config, "systemPromptArg");
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
argvBefore: Object.freeze([...config.argvBefore]),
|
|
33
|
+
argvAfter: Object.freeze([...config.argvAfter]),
|
|
34
|
+
...(modelArg === undefined ? {} : { modelArg }),
|
|
35
|
+
...(effortArg === undefined ? {} : { effortArg }),
|
|
36
|
+
...(systemPromptArg === undefined ? {} : { systemPromptArg }),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function optionAdmission(options, config) {
|
|
40
|
+
if (options.network !== undefined) {
|
|
41
|
+
return { kind: "refused", diagnostic: "ACP provider does not support the network option" };
|
|
42
|
+
}
|
|
43
|
+
if (options.model !== undefined && config.modelArg === undefined) {
|
|
44
|
+
return { kind: "refused", diagnostic: "ACP provider has no model argument mapping" };
|
|
45
|
+
}
|
|
46
|
+
if (options.effort !== undefined && config.effortArg === undefined) {
|
|
47
|
+
return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
|
|
48
|
+
}
|
|
49
|
+
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0 && config.systemPromptArg === undefined) {
|
|
50
|
+
return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
kind: "admitted",
|
|
54
|
+
options,
|
|
55
|
+
...(options.readonly === undefined ? {} : {
|
|
56
|
+
readonly: {
|
|
57
|
+
enforcement: "none",
|
|
58
|
+
diagnostic: "ACP cannot remove task-surface mutation capabilities",
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function argv(execution, config, options) {
|
|
64
|
+
if (execution.executable === undefined)
|
|
65
|
+
throw new Error("ACP provider execution requires executable");
|
|
66
|
+
const values = [execution.executable, ...config.argvBefore];
|
|
67
|
+
if (options.model !== undefined)
|
|
68
|
+
values.push(config.modelArg, options.model);
|
|
69
|
+
if (options.effort !== undefined)
|
|
70
|
+
values.push(config.effortArg, options.effort);
|
|
71
|
+
if (options.systemPrompt !== undefined && config.systemPromptArg !== undefined) {
|
|
72
|
+
values.push(config.systemPromptArg, options.systemPrompt);
|
|
73
|
+
}
|
|
74
|
+
values.push(...config.argvAfter);
|
|
75
|
+
return values;
|
|
76
|
+
}
|
|
77
|
+
export function createAcpProvider(execution, dependencies = {}) {
|
|
78
|
+
if (execution.executable === undefined)
|
|
79
|
+
throw new TypeError("ACP provider execution requires executable");
|
|
80
|
+
const config = decodeAcpConfig(execution.config);
|
|
81
|
+
const drive = async (input) => {
|
|
82
|
+
const launch = {
|
|
83
|
+
argv: argv(execution, config, input.options),
|
|
84
|
+
...(execution.env === undefined ? {} : { env: execution.env }),
|
|
85
|
+
};
|
|
86
|
+
return (await startAcpSession(launch, input, dependencies)).session;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
confinement: () => ({ kind: "unconfined" }),
|
|
90
|
+
admitOptions: (options) => optionAdmission(options, config),
|
|
91
|
+
start: async (input) => await drive(input),
|
|
92
|
+
resume: async (input) => await drive(input),
|
|
93
|
+
};
|
|
94
|
+
}
|