@midhunxavier/code-flow 0.0.0-stage → 0.1.0-preview.3
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/LICENSE +21 -0
- package/README.md +141 -2
- package/bin/codeflow.mjs +8 -0
- package/bin/launcher.mjs +127 -0
- package/dist/remote-host.json +1 -0
- package/licenses/inter.txt +92 -0
- package/licenses/preact.txt +21 -0
- package/licenses/signals-core.txt +21 -0
- package/licenses/signals.txt +21 -0
- package/licenses/typescript-notices.txt +193 -0
- package/licenses/typescript.txt +55 -0
- package/licenses/uqr.txt +22 -0
- package/licenses/xterm-fit.txt +19 -0
- package/licenses/xterm.txt +21 -0
- package/package.json +34 -4
- package/public/assets/changes-62ZIEQSN.js +1 -0
- package/public/assets/chunk-2PZVOKID.js +1 -0
- package/public/assets/chunk-4D5IENEB.js +1 -0
- package/public/assets/chunk-5SARP5MU.js +1 -0
- package/public/assets/chunk-63YLZ3PR.js +1 -0
- package/public/assets/chunk-7FUHKF2M.js +1 -0
- package/public/assets/chunk-APQEVJ2K.js +1 -0
- package/public/assets/chunk-CGOZQMEZ.js +2 -0
- package/public/assets/chunk-J4VYS3KV.js +2 -0
- package/public/assets/chunk-L7TUCIYF.js +1 -0
- package/public/assets/chunk-SIHDWM2Y.js +154 -0
- package/public/assets/chunk-TEJVYIKC.js +1 -0
- package/public/assets/code-browser-VB2HGZSG.js +4 -0
- package/public/assets/filtered-tasks-ES5CP4KV.js +1 -0
- package/public/assets/interactive-browser-7F5ZXRUV.js +7 -0
- package/public/assets/join-project-6RTFHNCA.js +1 -0
- package/public/assets/main-65EW73SB.js +1 -0
- package/public/assets/project-invitations-view-GMPWGWYM.js +1 -0
- package/public/assets/settings-UB5Q4AQM.js +9 -0
- package/public/assets/styles-18792BB4.css +32 -0
- package/public/assets/team-server-wizard-2GLDWS6D.js +8 -0
- package/public/assets/terminal-IWAIM4DE.js +62 -0
- package/public/assets.json +1 -0
- package/public/bundle-meta.json +1 -0
- package/public/docs/.nojekyll +0 -0
- package/public/docs/accessibility.html +19 -0
- package/public/docs/acp-agents.html +117 -0
- package/public/docs/agent-containers.html +79 -0
- package/public/docs/api.html +1130 -0
- package/public/docs/app.js +342 -0
- package/public/docs/application-improvements.html +275 -0
- package/public/docs/architecture-explorer.html +19 -0
- package/public/docs/architecture.html +149 -0
- package/public/docs/auto-team.html +148 -0
- package/public/docs/automations.html +109 -0
- package/public/docs/checkpoint-review.html +37 -0
- package/public/docs/code-hosts.html +35 -0
- package/public/docs/code-intelligence.html +35 -0
- package/public/docs/configuration.html +346 -0
- package/public/docs/context-inspection.html +23 -0
- package/public/docs/coordinated-changes.html +28 -0
- package/public/docs/deferred-runs.html +25 -0
- package/public/docs/dependency-discovery.html +19 -0
- package/public/docs/documentation.html +84 -0
- package/public/docs/dynamic-teams-evaluation.html +174 -0
- package/public/docs/dynamic-teams-implementation-plan.html +882 -0
- package/public/docs/dynamic-teams-implementation-review-1.html +141 -0
- package/public/docs/dynamic-teams-plan-review-0.html +204 -0
- package/public/docs/extensions.html +42 -0
- package/public/docs/external-sources.html +96 -0
- package/public/docs/filesystem-policy.html +26 -0
- package/public/docs/filtered-tasks.html +44 -0
- package/public/docs/font-license.txt +92 -0
- package/public/docs/getting-started.html +143 -0
- package/public/docs/github-review-threads.html +26 -0
- package/public/docs/github.html +157 -0
- package/public/docs/help.html +69 -0
- package/public/docs/index.html +88 -0
- package/public/docs/installation.html +80 -0
- package/public/docs/launch-profiles.html +48 -0
- package/public/docs/maintenance.html +60 -0
- package/public/docs/memory-claims.html +31 -0
- package/public/docs/memory-storage.html +58 -0
- package/public/docs/notifications.html +39 -0
- package/public/docs/patterns.html +195 -0
- package/public/docs/performance-results.html +210 -0
- package/public/docs/performance.html +166 -0
- package/public/docs/project-conventions.html +22 -0
- package/public/docs/project-first-navigation-plan.html +587 -0
- package/public/docs/project-teams-implementation-plan.html +772 -0
- package/public/docs/project-teams.html +129 -0
- package/public/docs/provider-compatibility.html +20 -0
- package/public/docs/releasing.html +65 -0
- package/public/docs/remote-workers.html +64 -0
- package/public/docs/review-inbox.html +26 -0
- package/public/docs/run-storage.html +23 -0
- package/public/docs/runs.html +141 -0
- package/public/docs/search.json +1 -0
- package/public/docs/security.html +133 -0
- package/public/docs/shared-projects.html +31 -0
- package/public/docs/ssh-projects.html +60 -0
- package/public/docs/style.css +768 -0
- package/public/docs/task-reruns.html +30 -0
- package/public/docs/team-collaboration.html +412 -0
- package/public/docs/verification.html +22 -0
- package/public/docs/vscode.html +53 -0
- package/public/docs/worker-permission-verification.html +131 -0
- package/public/index.html +17 -0
- package/scripts/check-node.mjs +7 -0
- package/scripts/codeflow.mjs +55 -0
- package/scripts/computer-use.swift +64 -0
- package/scripts/pty.py +273 -0
- package/src/acp-connections.js +110 -0
- package/src/adapters/acp-presets.js +37 -0
- package/src/adapters/acp-types.js +0 -0
- package/src/adapters/acp.js +328 -0
- package/src/adapters/agent-tools.js +28 -0
- package/src/adapters/agy-auth.js +67 -0
- package/src/adapters/agy-isolation.js +242 -0
- package/src/adapters/agy-tools.js +45 -0
- package/src/adapters/agy.js +326 -0
- package/src/adapters/claude-stream.js +58 -0
- package/src/adapters/claude.js +282 -0
- package/src/adapters/codex-app-server.js +555 -0
- package/src/adapters/codex.js +311 -0
- package/src/adapters/errors.js +57 -0
- package/src/adapters/index.js +91 -0
- package/src/adapters/managed-permissions.js +37 -0
- package/src/adapters/model-discovery.js +100 -0
- package/src/adapters/process.js +433 -0
- package/src/adapters/team-tools.js +103 -0
- package/src/adapters/types.js +0 -0
- package/src/agent-browser.js +688 -0
- package/src/agent-computer.js +88 -0
- package/src/agent-container.js +155 -0
- package/src/agent-team-definitions.js +131 -0
- package/src/agent-tool-client.mjs +79 -0
- package/src/agent-tools.js +296 -0
- package/src/approvals.js +134 -0
- package/src/architecture.js +456 -0
- package/src/assets.js +44 -0
- package/src/automation.js +901 -0
- package/src/browser-files.js +293 -0
- package/src/browser-ownership.js +150 -0
- package/src/certificate-renewal.js +75 -0
- package/src/chat-family.js +25 -0
- package/src/chat-handoff.js +87 -0
- package/src/checkpoint-review.js +72 -0
- package/src/checks.js +303 -0
- package/src/code-host-git.js +102 -0
- package/src/code-hosts-api.js +99 -0
- package/src/code-hosts.js +482 -0
- package/src/code-intelligence-worker.mjs +452 -0
- package/src/code-intelligence.js +141 -0
- package/src/collab/api.js +365 -0
- package/src/collab/checkpoints.js +198 -0
- package/src/collab/connections.js +139 -0
- package/src/collab/host-execution.js +347 -0
- package/src/collab/hub-client.js +210 -0
- package/src/collab/link-api.js +229 -0
- package/src/collab/link.js +595 -0
- package/src/collab/model.js +496 -0
- package/src/collab/runner.js +1014 -0
- package/src/collab/store.js +1452 -0
- package/src/collab/terminals.js +361 -0
- package/src/collaboration-reviews.js +116 -0
- package/src/config.js +48 -0
- package/src/context-budget.js +14 -0
- package/src/context-inspection.js +204 -0
- package/src/context.js +154 -0
- package/src/conventions.js +283 -0
- package/src/coordinated-api.js +247 -0
- package/src/deferred-launch.js +82 -0
- package/src/deferred-runs.js +515 -0
- package/src/dependency-health.js +35 -0
- package/src/diff.js +43 -0
- package/src/docs-help.js +316 -0
- package/src/doctor.js +232 -0
- package/src/documentation-changes.js +20 -0
- package/src/engine.js +5258 -0
- package/src/env.js +44 -0
- package/src/execution-budget.js +8 -0
- package/src/experience.js +50 -0
- package/src/extensions.js +144 -0
- package/src/filesystem-access.js +107 -0
- package/src/filesystem-policy.js +238 -0
- package/src/filtered-task-limits.js +93 -0
- package/src/filtered-tasks-api.js +53 -0
- package/src/filtered-tasks.js +576 -0
- package/src/frame-policy.js +66 -0
- package/src/fs.js +57 -0
- package/src/git.js +626 -0
- package/src/github-review-threads.js +181 -0
- package/src/github.js +538 -0
- package/src/http.js +69 -0
- package/src/improve-settings.js +38 -0
- package/src/improve.js +187 -0
- package/src/integrations.js +373 -0
- package/src/integrity.js +259 -0
- package/src/journey-api.js +77 -0
- package/src/journey-replay.js +65 -0
- package/src/journey-store.js +97 -0
- package/src/journeys.js +60 -0
- package/src/json.js +81 -0
- package/src/launch-profiles.js +292 -0
- package/src/learnings.js +80 -0
- package/src/local-app-url.js +19 -0
- package/src/log.js +39 -0
- package/src/maintenance.js +335 -0
- package/src/memory-analysis.js +182 -0
- package/src/memory-claims.js +153 -0
- package/src/memory-sqlite.js +236 -0
- package/src/memory.js +552 -0
- package/src/merge-validation.js +58 -0
- package/src/model-catalog.js +239 -0
- package/src/model-research.js +91 -0
- package/src/notes.js +176 -0
- package/src/notifications.js +413 -0
- package/src/patterns/auto-team-model.js +230 -0
- package/src/patterns/auto-team.js +816 -0
- package/src/patterns/council.js +199 -0
- package/src/patterns/debate.js +188 -0
- package/src/patterns/findings.js +141 -0
- package/src/patterns/index.js +21 -0
- package/src/patterns/loop.js +158 -0
- package/src/patterns/pipeline.js +125 -0
- package/src/patterns/prompts.js +181 -0
- package/src/patterns/race.js +147 -0
- package/src/patterns/schemas.js +40 -0
- package/src/patterns/solo.js +46 -0
- package/src/patterns/supervisor.js +235 -0
- package/src/patterns/task-draft.js +29 -0
- package/src/patterns/task-review.js +45 -0
- package/src/patterns/tdd.js +231 -0
- package/src/patterns/team-strategies.js +67 -0
- package/src/performance.js +43 -0
- package/src/persistence-status.js +35 -0
- package/src/phone-sessions.js +96 -0
- package/src/preview-overlay.js +392 -0
- package/src/preview.js +230 -0
- package/src/privacy.js +23 -0
- package/src/project-report.js +22 -0
- package/src/project-team-budget.js +22 -0
- package/src/project-team-model.js +225 -0
- package/src/project-team-policy.js +42 -0
- package/src/project-team-store.js +234 -0
- package/src/project-team-usage.js +24 -0
- package/src/project-teams-api.js +43 -0
- package/src/project-teams.js +644 -0
- package/src/projects.js +290 -0
- package/src/provider-eligibility.js +9 -0
- package/src/readiness.js +98 -0
- package/src/remote-tls.js +66 -0
- package/src/remote-workers/bundles.js +198 -0
- package/src/remote-workers/client.js +332 -0
- package/src/remote-workers/durability.js +37 -0
- package/src/remote-workers/protocol.js +68 -0
- package/src/remote-workers/stdio.js +138 -0
- package/src/remote-workers/worker.js +304 -0
- package/src/remote.js +204 -0
- package/src/repo-config.js +201 -0
- package/src/repo-coordination.js +725 -0
- package/src/repo-tree.js +106 -0
- package/src/repository-identity.js +13 -0
- package/src/retention-policy.js +466 -0
- package/src/review-inbox.js +38 -0
- package/src/routing.js +49 -0
- package/src/run-analysis.js +111 -0
- package/src/run-cold-store.js +231 -0
- package/src/run-environment.js +15 -0
- package/src/run-history.js +91 -0
- package/src/run-log.js +160 -0
- package/src/run-permissions.js +111 -0
- package/src/run-projection.js +77 -0
- package/src/run-recovery.js +53 -0
- package/src/run-status.js +56 -0
- package/src/serve.js +138 -0
- package/src/server.js +2871 -0
- package/src/shared-access.js +103 -0
- package/src/shared-accounts.js +381 -0
- package/src/shared-api.js +239 -0
- package/src/shell.js +346 -0
- package/src/ship.js +344 -0
- package/src/slots.js +74 -0
- package/src/snapshot.js +388 -0
- package/src/source-context.js +17 -0
- package/src/sse-writer.js +70 -0
- package/src/ssh-projects/bootstrap.mjs +95 -0
- package/src/ssh-projects/config.js +45 -0
- package/src/ssh-projects/gateway.js +143 -0
- package/src/ssh-projects/manager.js +320 -0
- package/src/ssh-projects/team-autostart.mjs +93 -0
- package/src/ssh-projects/team-remove.mjs +69 -0
- package/src/ssh-projects/team-status.mjs +39 -0
- package/src/ssh-projects/transport.js +133 -0
- package/src/stats.js +139 -0
- package/src/store.js +544 -0
- package/src/stream.js +91 -0
- package/src/sweeps.js +241 -0
- package/src/tailscale.js +101 -0
- package/src/task-draft.js +23 -0
- package/src/task-team.js +164 -0
- package/src/team-evaluation.js +243 -0
- package/src/team-mailbox.js +160 -0
- package/src/team-memory.js +84 -0
- package/src/team-rerun-graph.js +15 -0
- package/src/team-reruns.js +130 -0
- package/src/team-scheduler.js +121 -0
- package/src/team-steering.js +21 -0
- package/src/team-template-store.js +98 -0
- package/src/team-templates.js +117 -0
- package/src/team-tool-client.mjs +79 -0
- package/src/team-tools.js +235 -0
- package/src/teams.js +0 -0
- package/src/templates.js +62 -0
- package/src/transcript.js +134 -0
- package/src/verification-integrity.js +27 -0
- package/src/verification.js +183 -0
- package/src/watch.js +195 -0
- package/src/work-queue.js +47 -0
- package/src/workflow-control.js +83 -0
- package/src/workspace-api.js +207 -0
- package/src/workspace-changes.js +61 -0
- package/src/workspace.js +146 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { startJsonlProcess, commandExists, describeFailure, processErrorKind, stringify, truncate } from "./process.js";
|
|
5
|
+
const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
const string = (value) => typeof value === "string" && value.length > 0 && value.length <= 4e3;
|
|
7
|
+
class AcpError extends Error {
|
|
8
|
+
constructor(message, kind = "cli_incompatible", code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.kind = kind;
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
class AcpClient {
|
|
15
|
+
constructor(connection, opts, onUpdate) {
|
|
16
|
+
this.connection = connection;
|
|
17
|
+
this.opts = opts;
|
|
18
|
+
this.onUpdate = onUpdate;
|
|
19
|
+
this.proc = startJsonlProcess({
|
|
20
|
+
command: connection.command,
|
|
21
|
+
args: connection.args,
|
|
22
|
+
cwd: opts.cwd,
|
|
23
|
+
interactive: true,
|
|
24
|
+
timeoutMs: opts.timeoutMs,
|
|
25
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
26
|
+
resultGraceMs: 1e3,
|
|
27
|
+
onJson: (message) => {
|
|
28
|
+
try {
|
|
29
|
+
this.receive(message);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
this.fail(error);
|
|
32
|
+
this.proc.kill();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
onText: () => {
|
|
36
|
+
this.fail(new AcpError("The CLI wrote invalid ACP data to stdout. Configure its ACP subcommand; logs must go to stderr."));
|
|
37
|
+
this.proc.kill();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
void this.proc.outcome.then((outcome) => {
|
|
41
|
+
this.fail(new AcpError(describeFailure(connection.name, outcome), processErrorKind(outcome) ?? "other"));
|
|
42
|
+
});
|
|
43
|
+
opts.signal?.addEventListener("abort", this.cancel, { once: true });
|
|
44
|
+
if (opts.signal?.aborted) this.cancel();
|
|
45
|
+
}
|
|
46
|
+
nextId = 0;
|
|
47
|
+
pending = /* @__PURE__ */ new Map();
|
|
48
|
+
stopped;
|
|
49
|
+
lifetime = new AbortController();
|
|
50
|
+
cancelTimer;
|
|
51
|
+
approvalCount = 0;
|
|
52
|
+
permissionIds = /* @__PURE__ */ new Set();
|
|
53
|
+
permissionTasks = /* @__PURE__ */ new Set();
|
|
54
|
+
toolDetails = /* @__PURE__ */ new Map();
|
|
55
|
+
sessionId;
|
|
56
|
+
prompting = false;
|
|
57
|
+
proc;
|
|
58
|
+
fail(error) {
|
|
59
|
+
this.stopped ??= error;
|
|
60
|
+
this.lifetime.abort();
|
|
61
|
+
for (const waiter of this.pending.values()) {
|
|
62
|
+
clearTimeout(waiter.timer);
|
|
63
|
+
waiter.reject(this.stopped);
|
|
64
|
+
}
|
|
65
|
+
this.pending.clear();
|
|
66
|
+
}
|
|
67
|
+
cancel = () => {
|
|
68
|
+
if (this.sessionId) this.proc.send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: this.sessionId } });
|
|
69
|
+
this.fail(new AcpError("ACP turn was cancelled", "cancelled"));
|
|
70
|
+
this.cancelTimer = setTimeout(() => this.proc.kill(), 1e3);
|
|
71
|
+
this.cancelTimer.unref();
|
|
72
|
+
};
|
|
73
|
+
request(method, params, timeoutMs = 2e4) {
|
|
74
|
+
if (this.stopped) return Promise.reject(this.stopped);
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const id = ++this.nextId;
|
|
77
|
+
const timer = timeoutMs ? setTimeout(() => {
|
|
78
|
+
this.pending.delete(id);
|
|
79
|
+
reject(new AcpError(`ACP ${method} did not respond in time`, "timeout"));
|
|
80
|
+
}, timeoutMs) : void 0;
|
|
81
|
+
timer?.unref();
|
|
82
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
83
|
+
if (!this.proc.send({ jsonrpc: "2.0", id, method, params })) {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
this.pending.delete(id);
|
|
86
|
+
reject(new AcpError("ACP process closed its input", "other"));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
receive(message) {
|
|
91
|
+
if (!object(message) || message.jsonrpc !== "2.0") throw new AcpError("The CLI did not return ACP JSON-RPC 2.0 messages");
|
|
92
|
+
if (message.method === void 0) {
|
|
93
|
+
const waiter = this.pending.get(message.id);
|
|
94
|
+
if (!waiter) return;
|
|
95
|
+
this.pending.delete(message.id);
|
|
96
|
+
clearTimeout(waiter.timer);
|
|
97
|
+
if (object(message.error)) waiter.reject(new AcpError(truncate(String(message.error.message ?? "ACP request failed")), message.error.code === -32e3 ? "auth" : message.error.code === -32002 ? "session_not_found" : [-32601, -32602].includes(message.error.code) ? "cli_incompatible" : "other", message.error.code));
|
|
98
|
+
else if (object(message.result)) waiter.resolve(message.result);
|
|
99
|
+
else waiter.reject(new AcpError("ACP response is missing its result"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (message.id !== void 0) {
|
|
103
|
+
if (!(typeof message.id === "number" || typeof message.id === "string")) throw new AcpError("Invalid ACP request ID");
|
|
104
|
+
if (message.method === "session/request_permission") {
|
|
105
|
+
if (this.permissionIds.has(message.id) || this.permissionIds.size >= 16) throw new AcpError("Too many or duplicate ACP permission requests");
|
|
106
|
+
this.permissionIds.add(message.id);
|
|
107
|
+
const task = this.permission(message).finally(() => {
|
|
108
|
+
this.permissionIds.delete(message.id);
|
|
109
|
+
this.permissionTasks.delete(task);
|
|
110
|
+
});
|
|
111
|
+
this.permissionTasks.add(task);
|
|
112
|
+
} else this.proc.send({ jsonrpc: "2.0", id: message.id, error: { code: -32601, message: "Client capability is not supported" } });
|
|
113
|
+
} else if (message.method === "session/update" && this.prompting && message.params?.sessionId === this.sessionId && object(message.params.update)) {
|
|
114
|
+
const update = message.params.update;
|
|
115
|
+
if (["tool_call", "tool_call_update"].includes(update.sessionUpdate) && string(update.toolCallId)) {
|
|
116
|
+
if (!this.toolDetails.has(update.toolCallId) && this.toolDetails.size >= 2e3) throw new AcpError("ACP turn exceeds the tool call limit");
|
|
117
|
+
const previous = this.toolDetails.get(update.toolCallId) ?? {};
|
|
118
|
+
this.toolDetails.set(update.toolCallId, {
|
|
119
|
+
...previous,
|
|
120
|
+
toolCallId: update.toolCallId,
|
|
121
|
+
...typeof update.kind === "string" ? { kind: update.kind } : {},
|
|
122
|
+
...typeof update.title === "string" ? { title: truncate(update.title, 1e3) } : {},
|
|
123
|
+
...Array.isArray(update.locations) ? { locations: update.locations.slice(0, 50).filter((item) => string(item?.path)).map((item) => ({ path: item.path })) } : {},
|
|
124
|
+
...update.rawInput !== void 0 ? { rawInput: { description: truncate(stringify(update.rawInput)), ...typeof update.rawInput?.command === "string" ? { command: truncate(update.rawInput.command) } : {}, ...string(update.rawInput?.path) ? { path: update.rawInput.path } : {} } } : {}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
this.onUpdate(update);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async permission(message) {
|
|
131
|
+
const params = message.params;
|
|
132
|
+
const tool = object(params?.toolCall) ? { ...this.toolDetails.get(params.toolCall.toolCallId), ...params.toolCall } : void 0;
|
|
133
|
+
let optionId;
|
|
134
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
135
|
+
const once = (kind) => options.find((option) => option?.kind === kind && string(option.optionId))?.optionId;
|
|
136
|
+
const eligible = this.prompting && params?.sessionId === this.sessionId && object(tool) && string(tool.toolCallId);
|
|
137
|
+
const canAsk = eligible && (this.opts.mode === "write" || ["read", "search", "think"].includes(tool.kind));
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
let waiting = false;
|
|
140
|
+
const abort = () => controller.abort();
|
|
141
|
+
this.lifetime.signal.addEventListener("abort", abort, { once: true });
|
|
142
|
+
if (this.lifetime.signal.aborted) controller.abort();
|
|
143
|
+
const timer = setTimeout(abort, 12e4);
|
|
144
|
+
timer.unref();
|
|
145
|
+
try {
|
|
146
|
+
if (canAsk && once("allow_once") && this.opts.interactiveApprovals && this.opts.onApproval && !controller.signal.aborted) {
|
|
147
|
+
waiting = true;
|
|
148
|
+
this.approvalCount++;
|
|
149
|
+
this.proc.pauseIdle(true);
|
|
150
|
+
const paths = Array.isArray(tool.locations) ? tool.locations.slice(0, 50).map((location) => location?.path).filter(string) : [];
|
|
151
|
+
if (!paths.length && tool.kind === "edit" && string(tool.rawInput?.path)) paths.push(tool.rawInput.path);
|
|
152
|
+
const request = {
|
|
153
|
+
id: `acp:${String(message.id)}`,
|
|
154
|
+
kind: ["edit", "delete", "move"].includes(tool.kind) ? "file-change" : "command",
|
|
155
|
+
paths,
|
|
156
|
+
cwd: this.opts.cwd,
|
|
157
|
+
command: typeof tool.rawInput?.command === "string" ? truncate(tool.rawInput.command) : truncate(stringify(tool.rawInput ?? tool.title ?? tool.kind)),
|
|
158
|
+
reason: truncate(String(tool.title ?? "ACP tool request"))
|
|
159
|
+
};
|
|
160
|
+
const decision = await Promise.race([
|
|
161
|
+
this.opts.onApproval(request, controller.signal),
|
|
162
|
+
new Promise((resolve) => {
|
|
163
|
+
if (controller.signal.aborted) resolve("decline");
|
|
164
|
+
else controller.signal.addEventListener("abort", () => resolve("decline"), { once: true });
|
|
165
|
+
})
|
|
166
|
+
]);
|
|
167
|
+
if (decision === "approve" && !controller.signal.aborted) optionId = once("allow_once");
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
} finally {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
this.lifetime.signal.removeEventListener("abort", abort);
|
|
173
|
+
controller.abort();
|
|
174
|
+
if (waiting) {
|
|
175
|
+
this.approvalCount--;
|
|
176
|
+
if (!this.approvalCount) this.proc.pauseIdle(false);
|
|
177
|
+
}
|
|
178
|
+
optionId ??= once("reject_once");
|
|
179
|
+
this.proc.send({ jsonrpc: "2.0", id: message.id, result: { outcome: this.lifetime.signal.aborted || !optionId ? { outcome: "cancelled" } : { outcome: "selected", optionId } } });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async initialize() {
|
|
183
|
+
const init = await this.request("initialize", { protocolVersion: 1, clientCapabilities: {}, clientInfo: { name: "code-flow", version: "0.1.0" } });
|
|
184
|
+
if (init.protocolVersion !== 1) throw new AcpError(`Unsupported ACP protocol version: ${String(init.protocolVersion)}; code-flow supports v1`);
|
|
185
|
+
return init;
|
|
186
|
+
}
|
|
187
|
+
async session(init) {
|
|
188
|
+
const params = { cwd: this.opts.cwd, mcpServers: [] };
|
|
189
|
+
let session;
|
|
190
|
+
if (this.opts.resumeSessionId) {
|
|
191
|
+
if (init.agentCapabilities?.loadSession !== true) throw new AcpError("This ACP agent cannot load previous sessions", "session_not_found");
|
|
192
|
+
session = await this.request("session/load", { ...params, sessionId: this.opts.resumeSessionId });
|
|
193
|
+
this.sessionId = this.opts.resumeSessionId;
|
|
194
|
+
} else {
|
|
195
|
+
session = await this.request("session/new", params);
|
|
196
|
+
if (!string(session.sessionId)) throw new AcpError("ACP session/new returned no valid session ID");
|
|
197
|
+
this.sessionId = session.sessionId;
|
|
198
|
+
}
|
|
199
|
+
return session;
|
|
200
|
+
}
|
|
201
|
+
async close() {
|
|
202
|
+
this.opts.signal?.removeEventListener("abort", this.cancel);
|
|
203
|
+
clearTimeout(this.cancelTimer);
|
|
204
|
+
this.lifetime.abort();
|
|
205
|
+
await Promise.allSettled([...this.permissionTasks]);
|
|
206
|
+
this.proc.resultSeen();
|
|
207
|
+
this.proc.endInput();
|
|
208
|
+
await this.proc.outcome;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function configOption(session, category) {
|
|
212
|
+
const options = Array.isArray(session?.configOptions) ? session.configOptions.filter((option) => option?.type === "select" && option.category === category && string(option.id)) : [];
|
|
213
|
+
return options.find((option) => option.id === category) ?? options.find((option) => category !== "model" || option.id !== "provider");
|
|
214
|
+
}
|
|
215
|
+
function choices(option) {
|
|
216
|
+
const flat = Array.isArray(option?.options) ? option.options.flatMap((item) => Array.isArray(item?.options) ? item.options : [item]) : [];
|
|
217
|
+
return flat.filter((item) => string(item?.value) && string(item?.name)).slice(0, 1e3).map((item) => ({ id: item.value, label: item.name }));
|
|
218
|
+
}
|
|
219
|
+
function acpCatalog(session) {
|
|
220
|
+
const model = configOption(session, "model"), effort = configOption(session, "thought_level");
|
|
221
|
+
const models = model ? choices(model) : Array.isArray(session?.models?.availableModels) ? session.models.availableModels.filter((item) => string(item?.modelId) && string(item?.name)).slice(0, 1e3).map((item) => ({ id: item.modelId, label: item.name })) : [];
|
|
222
|
+
const defaultModel = model?.currentValue ?? session?.models?.currentModelId;
|
|
223
|
+
return {
|
|
224
|
+
models,
|
|
225
|
+
efforts: choices(effort).map((item) => item.id),
|
|
226
|
+
allowCustom: false,
|
|
227
|
+
defaultModel: string(defaultModel) ? defaultModel : void 0,
|
|
228
|
+
defaultEffort: string(effort?.currentValue) ? effort.currentValue : void 0
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async function probeAcp(connection) {
|
|
232
|
+
const cwd = await mkdtemp(join(tmpdir(), "codeflow-acp-probe-"));
|
|
233
|
+
let client;
|
|
234
|
+
try {
|
|
235
|
+
client = new AcpClient(connection, { cwd, prompt: "", mode: "read", timeoutMs: 3e4 }, () => {
|
|
236
|
+
});
|
|
237
|
+
const init = await client.initialize(), session = await client.session(init);
|
|
238
|
+
return {
|
|
239
|
+
protocolVersion: 1,
|
|
240
|
+
agent: string(init.agentInfo?.title) ? init.agentInfo.title : string(init.agentInfo?.name) ? init.agentInfo.name : connection.name,
|
|
241
|
+
version: string(init.agentInfo?.version) ? init.agentInfo.version : void 0,
|
|
242
|
+
sessionResume: init.agentCapabilities?.loadSession === true,
|
|
243
|
+
catalog: acpCatalog(session)
|
|
244
|
+
};
|
|
245
|
+
} finally {
|
|
246
|
+
await client?.close();
|
|
247
|
+
await rm(cwd, { recursive: true, force: true });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function createAcpAdapter(connection) {
|
|
251
|
+
const saved = structuredClone(connection);
|
|
252
|
+
return {
|
|
253
|
+
id: saved.id,
|
|
254
|
+
displayName: saved.name,
|
|
255
|
+
binary: saved.command,
|
|
256
|
+
available: () => commandExists(saved.command),
|
|
257
|
+
catalog: async () => (await probeAcp(saved)).catalog,
|
|
258
|
+
run: (opts, event) => runAcp(saved, opts, event)
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function runAcp(connection, opts, event) {
|
|
262
|
+
if (opts.signal?.aborted) return { ok: false, finalText: "", error: "ACP turn was cancelled", errorKind: "cancelled" };
|
|
263
|
+
if (opts.agentContainer || opts.trust !== "full") return { ok: false, finalText: "", error: "ACP connections require Full access on the current host; sandbox and container profiles are not supported", errorKind: "cli_incompatible" };
|
|
264
|
+
let finalText = "", client;
|
|
265
|
+
const calls = /* @__PURE__ */ new Map(), finished = /* @__PURE__ */ new Set();
|
|
266
|
+
try {
|
|
267
|
+
client = new AcpClient(connection, opts, (update) => {
|
|
268
|
+
if (update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk") {
|
|
269
|
+
if (update.content?.type !== "text" || typeof update.content.text !== "string") return;
|
|
270
|
+
if (update.sessionUpdate === "agent_message_chunk") {
|
|
271
|
+
if (finalText.length + update.content.text.length > 4 * 1024 * 1024) throw new AcpError("ACP answer exceeds the 4 MiB limit");
|
|
272
|
+
finalText += update.content.text;
|
|
273
|
+
event({ kind: "text", text: update.content.text });
|
|
274
|
+
} else event({ kind: "thinking", text: update.content.text });
|
|
275
|
+
} else if (["tool_call", "tool_call_update"].includes(update.sessionUpdate) && string(update.toolCallId)) {
|
|
276
|
+
const previous = calls.get(update.toolCallId);
|
|
277
|
+
if (!previous && calls.size >= 2e3) throw new AcpError("ACP turn exceeds the tool call limit");
|
|
278
|
+
const name = String(update.title ?? previous?.name ?? update.kind ?? "Tool");
|
|
279
|
+
const input = update.rawInput !== void 0 ? truncate(stringify(update.rawInput), 16e3) : previous?.input ?? "{}";
|
|
280
|
+
if (!previous || previous.name !== name || previous.input !== input) {
|
|
281
|
+
event({ kind: "tool_call", callId: update.toolCallId, name, input });
|
|
282
|
+
}
|
|
283
|
+
const text = Array.isArray(update.content) ? update.content.map((item) => item.type === "content" && item.content?.type === "text" ? item.content.text : item.type === "diff" ? `${item.path}
|
|
284
|
+
${item.newText ?? ""}` : "").join("\n") : void 0;
|
|
285
|
+
const output = truncate(text ?? (update.rawOutput !== void 0 ? stringify(update.rawOutput) : previous?.output ?? ""), 32e3);
|
|
286
|
+
calls.set(update.toolCallId, { name, input, output });
|
|
287
|
+
if (["completed", "failed"].includes(update.status) && !finished.has(update.toolCallId)) {
|
|
288
|
+
finished.add(update.toolCallId);
|
|
289
|
+
event({ kind: "tool_result", callId: update.toolCallId, output, isError: update.status === "failed" });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
const init = await client.initialize();
|
|
294
|
+
let session = await client.session(init);
|
|
295
|
+
event({ kind: "session", sessionId: client.sessionId });
|
|
296
|
+
for (const [category, value] of [["model", opts.model], ["thought_level", opts.effort]]) {
|
|
297
|
+
if (!value) continue;
|
|
298
|
+
const option = configOption(session, category);
|
|
299
|
+
if (option) {
|
|
300
|
+
if (!choices(option).some((item) => item.id === value)) throw new AcpError(`ACP agent does not advertise ${category} ${value}`);
|
|
301
|
+
session = { ...session, ...await client.request("session/set_config_option", { sessionId: client.sessionId, configId: option.id, value }) };
|
|
302
|
+
} else if (category === "model" && acpCatalog(session).models.some((model) => model.id === value)) {
|
|
303
|
+
await client.request("session/set_model", { sessionId: client.sessionId, modelId: value });
|
|
304
|
+
} else throw new AcpError(`This ACP agent does not support selecting ${category}`);
|
|
305
|
+
}
|
|
306
|
+
const prompt = `${opts.mode === "read" ? "This is a read-only task. Do not edit files or run commands that change the repository.\n\n" : ""}${opts.prompt}${opts.jsonSchema ? `
|
|
307
|
+
|
|
308
|
+
Return your final answer as JSON matching this schema:
|
|
309
|
+
${JSON.stringify(opts.jsonSchema)}` : ""}`;
|
|
310
|
+
client.prompting = true;
|
|
311
|
+
const result = await client.request("session/prompt", { sessionId: client.sessionId, prompt: [{ type: "text", text: prompt }] }, 0);
|
|
312
|
+
client.prompting = false;
|
|
313
|
+
if (opts.signal?.aborted || result.stopReason === "cancelled") throw new AcpError("ACP turn was cancelled", "cancelled");
|
|
314
|
+
if (result.stopReason !== "end_turn") throw new AcpError(`ACP turn stopped: ${String(result.stopReason ?? "missing stop reason")}`, "other");
|
|
315
|
+
return { ok: true, finalText, sessionId: client.sessionId };
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const failure = error;
|
|
318
|
+
return { ok: false, finalText, sessionId: client?.sessionId, error: failure.message, errorKind: opts.signal?.aborted ? "cancelled" : error instanceof AcpError ? error.kind : "other" };
|
|
319
|
+
} finally {
|
|
320
|
+
await client?.close();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
acpCatalog,
|
|
325
|
+
createAcpAdapter,
|
|
326
|
+
probeAcp,
|
|
327
|
+
runAcp
|
|
328
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { AGENT_TOOLS } from "../agent-tools.js";
|
|
6
|
+
function agentMcpConfig(connection) {
|
|
7
|
+
if (!/^http:\/\/127\.0\.0\.1:\d+\/v1\/agent-tools$/.test(connection.endpoint) || !/^[\w-]{43}$/.test(connection.token) || !Number.isFinite(connection.expiresAt) || connection.expiresAt <= Date.now()) throw new Error("Invalid task tool connection");
|
|
8
|
+
return { serverUrl: connection.endpoint.replace(/agent-tools$/, "agent-mcp"), headers: { Authorization: `Bearer ${connection.token}` }, tools: Object.fromEntries(AGENT_TOOLS.map((tool) => [tool.name, { eager: true }])) };
|
|
9
|
+
}
|
|
10
|
+
async function prepareAgentTools(connection) {
|
|
11
|
+
const dir = await mkdtemp(join(tmpdir(), "codeflow-agent-tools-"));
|
|
12
|
+
try {
|
|
13
|
+
const path = join(dir, "connection.json"), config = join(dir, "mcp.json");
|
|
14
|
+
await writeFile(path, JSON.stringify(connection), { mode: 384 });
|
|
15
|
+
const args = [fileURLToPath(new URL("../agent-tool-client.mjs", import.meta.url)), "--connection", path];
|
|
16
|
+
const value = { command: process.execPath, args, enabled_tools: AGENT_TOOLS.map((tool) => tool.name), startup_timeout_sec: 10, tool_timeout_sec: 660, tools: Object.fromEntries(AGENT_TOOLS.map((tool) => [tool.name, { approval_mode: "approve" }])) };
|
|
17
|
+
await writeFile(config, JSON.stringify({ mcpServers: { codeflow_agent: { type: "stdio", command: process.execPath, args } } }), { mode: 384 });
|
|
18
|
+
const toml = `mcp_servers.codeflow_agent={command=${JSON.stringify(process.execPath)},args=${JSON.stringify(args)},enabled_tools=${JSON.stringify(value.enabled_tools)},startup_timeout_sec=10,tool_timeout_sec=660,tools={${AGENT_TOOLS.map((tool) => `${tool.name}={approval_mode="approve"}`).join(",")}}}`;
|
|
19
|
+
return { claudeConfig: config, codexConfig: ["-c", toml], codexThreadConfig: { "mcp_servers.codeflow_agent": value }, cleanup: () => rm(dir, { recursive: true, force: true }) };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
await rm(dir, { recursive: true, force: true });
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
agentMcpConfig,
|
|
27
|
+
prepareAgentTools
|
|
28
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
function agyCredentialFailure(text) {
|
|
4
|
+
return /(?:token refresh failed|failed to refresh loaded token|failed to (?:save|persist|marshal) (?:refreshed (?:OAuth )?token|credentials)|failed to (?:save|persist) token to keyring|failed to load (?:stored )?token from keyring, falling back to file|stored credentials are invalid or expired)/i.test(text);
|
|
5
|
+
}
|
|
6
|
+
const AGY_AUTH_LOG_LIMIT = 4 * 1024 * 1024;
|
|
7
|
+
function observeAgyAuthLine(observation, line) {
|
|
8
|
+
const message = line.match(/\b(?:auth|auth_provider|server_oauth|credentials|keyring)\.go:\d+\]\s+(.*)$/)?.[1];
|
|
9
|
+
if (!message) return;
|
|
10
|
+
if (/token refreshed, new expiry=|Auto-saving refreshed token from token source callback/.test(message)) observation.refreshObserved = true;
|
|
11
|
+
if (agyCredentialFailure(message)) observation.credentialFailure = true;
|
|
12
|
+
if (/OAuth: starting OAuth flow/.test(message)) observation.interactiveAuthObserved = true;
|
|
13
|
+
const expired = message.match(/keyringAuth: loaded token, [^\r\n]*\bexpired=(true|false)\b/)?.[1];
|
|
14
|
+
if (expired) observation.loadedTokenExpired = observation.loadedTokenExpired === true || expired === "true";
|
|
15
|
+
}
|
|
16
|
+
async function inspectAgyAuthLog(path) {
|
|
17
|
+
const result = { log: "unreadable", refreshObserved: false, credentialFailure: false, interactiveAuthObserved: false, loadedTokenExpired: null };
|
|
18
|
+
let file;
|
|
19
|
+
try {
|
|
20
|
+
file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
21
|
+
const stat = await file.stat();
|
|
22
|
+
if (!stat.isFile() || stat.nlink !== 1) return result;
|
|
23
|
+
if (stat.size > AGY_AUTH_LOG_LIMIT) {
|
|
24
|
+
result.log = "too-large";
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
const buffer = Buffer.alloc(16 * 1024);
|
|
28
|
+
let consumed = 0, pending = "", dropped = false;
|
|
29
|
+
while (consumed <= AGY_AUTH_LOG_LIMIT) {
|
|
30
|
+
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, AGY_AUTH_LOG_LIMIT + 1 - consumed), consumed);
|
|
31
|
+
if (!bytesRead) break;
|
|
32
|
+
consumed += bytesRead;
|
|
33
|
+
if (consumed > AGY_AUTH_LOG_LIMIT) {
|
|
34
|
+
result.log = "too-large";
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
for (const part of buffer.subarray(0, bytesRead).toString("latin1").split(/(?<=\n)/)) {
|
|
38
|
+
if (!dropped) {
|
|
39
|
+
pending += part;
|
|
40
|
+
if (pending.length > 8192) {
|
|
41
|
+
pending = "";
|
|
42
|
+
dropped = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (part.endsWith("\n")) {
|
|
46
|
+
if (!dropped) observeAgyAuthLine(result, pending.trimEnd());
|
|
47
|
+
pending = "";
|
|
48
|
+
dropped = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (pending && !dropped) observeAgyAuthLine(result, pending);
|
|
53
|
+
result.log = "read";
|
|
54
|
+
return result;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
result.log = error.code === "ENOENT" ? "missing" : "unreadable";
|
|
57
|
+
return result;
|
|
58
|
+
} finally {
|
|
59
|
+
await file?.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
AGY_AUTH_LOG_LIMIT,
|
|
64
|
+
agyCredentialFailure,
|
|
65
|
+
inspectAgyAuthLog,
|
|
66
|
+
observeAgyAuthLine
|
|
67
|
+
};
|