@midhunxavier/code-flow 0.0.0-stage → 0.1.0-preview.1
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 +122 -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/browser-7XI52A6V.js +8 -0
- package/public/assets/changes-EYFPASEI.js +1 -0
- package/public/assets/chunk-7H3ZLTKA.js +2 -0
- package/public/assets/chunk-L7TUCIYF.js +1 -0
- package/public/assets/chunk-LLMQG23J.js +163 -0
- package/public/assets/main-KIRM2OEK.js +1 -0
- package/public/assets/settings-QJZVW22Y.js +9 -0
- package/public/assets/styles-615EBB20.css +32 -0
- package/public/assets/terminal-A2FI2NAX.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 +142 -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 +118 -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 +77 -0
- package/public/docs/installation.html +74 -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 +99 -0
- package/public/docs/provider-compatibility.html +20 -0
- package/public/docs/releasing.html +61 -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 +379 -0
- package/public/docs/verification.html +22 -0
- package/public/docs/vscode.html +53 -0
- package/public/docs/worker-permission-verification.html +122 -0
- package/public/index.html +17 -0
- package/scripts/check-node.mjs +7 -0
- package/scripts/codeflow.mjs +55 -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/agy-auth.js +67 -0
- package/src/adapters/agy-isolation.js +223 -0
- package/src/adapters/agy.js +297 -0
- package/src/adapters/claude.js +220 -0
- package/src/adapters/codex-app-server.js +518 -0
- package/src/adapters/codex.js +299 -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 +418 -0
- package/src/adapters/team-tools.js +102 -0
- package/src/adapters/types.js +0 -0
- package/src/agent-container.js +155 -0
- package/src/approvals.js +121 -0
- package/src/architecture.js +456 -0
- package/src/assets.js +44 -0
- package/src/automation.js +901 -0
- package/src/certificate-renewal.js +75 -0
- package/src/chat-handoff.js +55 -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/engine.js +4366 -0
- package/src/env.js +44 -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/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/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 +238 -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 +212 -0
- package/src/patterns/auto-team.js +659 -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 +19 -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 +49 -0
- package/src/patterns/supervisor.js +235 -0
- package/src/patterns/task-draft.js +29 -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 +383 -0
- package/src/preview.js +219 -0
- package/src/privacy.js +23 -0
- package/src/project-report.js +22 -0
- package/src/project-team-model.js +178 -0
- package/src/project-team-policy.js +42 -0
- package/src/project-team-store.js +213 -0
- package/src/project-teams-api.js +36 -0
- package/src/project-teams.js +551 -0
- package/src/projects.js +286 -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 +732 -0
- package/src/repo-tree.js +106 -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 +110 -0
- package/src/run-projection.js +75 -0
- package/src/run-recovery.js +53 -0
- package/src/run-status.js +54 -0
- package/src/serve.js +138 -0
- package/src/server.js +2738 -0
- package/src/shared-access.js +103 -0
- package/src/shared-accounts.js +301 -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 +116 -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/team-evaluation.js +242 -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-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 +176 -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
package/scripts/pty.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""code-flow's pseudo-terminal for the side panel's Terminal (src/shell.ts starts it).
|
|
2
|
+
|
|
3
|
+
python3 pty.py COLS ROWS PROGRAM ARGV0 [ARG...]
|
|
4
|
+
|
|
5
|
+
Runs PROGRAM (your login shell) in a new pseudo-terminal, so pagers, prompts, colours and
|
|
6
|
+
full-screen programs work, without a native npm module. Bytes are relayed as they are:
|
|
7
|
+
|
|
8
|
+
fd 0 keystrokes in fd 1 what the terminal shows, out
|
|
9
|
+
fd 3 control, JSON lines both ways:
|
|
10
|
+
in {"resize": [cols, rows]}
|
|
11
|
+
out {"pid": n, "tty": "/dev/ttys004"} once, then {"fg": pgid} whenever the
|
|
12
|
+
terminal's foreground process group changes (PROGRAM's own pid: nothing
|
|
13
|
+
else is running in the foreground)
|
|
14
|
+
|
|
15
|
+
It stops when PROGRAM exits, on SIGTERM/SIGHUP/SIGINT, or when fd 0 closes (code-flow went
|
|
16
|
+
away). Everything still in PROGRAM's session (what it started, unless that called setsid) then
|
|
17
|
+
gets SIGHUP (and SIGCONT, for stopped jobs), and SIGKILL a second later. Exits with PROGRAM's exit code, or 128 + the signal that ended it.
|
|
18
|
+
Standard library only; Python 3.6 or later.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import errno
|
|
22
|
+
import fcntl
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import select
|
|
26
|
+
import signal
|
|
27
|
+
import struct
|
|
28
|
+
import subprocess
|
|
29
|
+
import sys
|
|
30
|
+
import termios
|
|
31
|
+
import time
|
|
32
|
+
|
|
33
|
+
CONTROL = 3
|
|
34
|
+
GRACE = 1.0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_size(fd, cols, rows):
|
|
38
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def write_all(fd, data):
|
|
42
|
+
"""Write everything, waiting when the other end is full (a non-blocking pipe)."""
|
|
43
|
+
view = memoryview(data)
|
|
44
|
+
while view:
|
|
45
|
+
try:
|
|
46
|
+
view = view[os.write(fd, view):]
|
|
47
|
+
except BlockingIOError:
|
|
48
|
+
select.select([], [fd], [], 1.0)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def start(program, argv, cols, rows, control_open):
|
|
52
|
+
master, slave = os.openpty()
|
|
53
|
+
set_size(slave, cols, rows)
|
|
54
|
+
tty = os.ttyname(slave)
|
|
55
|
+
pid = os.fork()
|
|
56
|
+
if pid == 0:
|
|
57
|
+
try:
|
|
58
|
+
os.close(master)
|
|
59
|
+
if control_open:
|
|
60
|
+
os.close(CONTROL)
|
|
61
|
+
os.setsid() # a session of its own, with this terminal as its controlling terminal
|
|
62
|
+
if hasattr(termios, "TIOCSCTTY"):
|
|
63
|
+
fcntl.ioctl(slave, termios.TIOCSCTTY, 0)
|
|
64
|
+
else:
|
|
65
|
+
os.close(os.open(tty, os.O_RDWR))
|
|
66
|
+
for fd in (0, 1, 2):
|
|
67
|
+
os.dup2(slave, fd)
|
|
68
|
+
if slave > 2:
|
|
69
|
+
os.close(slave)
|
|
70
|
+
# Python ignores these at start-up, and ignored signals survive exec: `yes | head`
|
|
71
|
+
# would complain about a broken pipe instead of just stopping.
|
|
72
|
+
for sig in ("SIGPIPE", "SIGXFSZ"):
|
|
73
|
+
if hasattr(signal, sig):
|
|
74
|
+
signal.signal(getattr(signal, sig), signal.SIG_DFL)
|
|
75
|
+
os.execv(program, argv)
|
|
76
|
+
except BaseException as err: # noqa: B902 (anything: report it on the terminal and stop)
|
|
77
|
+
os.write(2, ("couldn't start %s: %s\r\n" % (program, err)).encode())
|
|
78
|
+
os._exit(127)
|
|
79
|
+
os.close(slave)
|
|
80
|
+
return pid, master, tty
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def groups_of(session):
|
|
84
|
+
"""The process groups of everything in PROGRAM's session: what it started, still running.
|
|
85
|
+
|
|
86
|
+
By session, not by terminal: once PROGRAM (the session's leader) has exited, its jobs no longer
|
|
87
|
+
have a controlling terminal, but they keep its session.
|
|
88
|
+
"""
|
|
89
|
+
if os.path.isdir("/proc/self"):
|
|
90
|
+
pids = [int(entry) for entry in os.listdir("/proc") if entry.isdigit()] # Linux: no ps needed
|
|
91
|
+
else:
|
|
92
|
+
try:
|
|
93
|
+
out = subprocess.run(["ps", "-ax", "-o", "pid="], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=5).stdout
|
|
94
|
+
pids = [int(word) for word in out.split() if word.isdigit()]
|
|
95
|
+
except (OSError, subprocess.SubprocessError):
|
|
96
|
+
pids = []
|
|
97
|
+
found = {session}
|
|
98
|
+
for pid in pids:
|
|
99
|
+
try:
|
|
100
|
+
if os.getsid(pid) == session:
|
|
101
|
+
found.add(os.getpgid(pid))
|
|
102
|
+
except OSError:
|
|
103
|
+
pass # gone, or not ours to look at
|
|
104
|
+
return {g for g in found if g > 1 and g != os.getpgrp()}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def hang_up(pid):
|
|
108
|
+
"""SIGHUP to everything PROGRAM started, as when a terminal window closes; SIGKILL to what's left."""
|
|
109
|
+
groups = groups_of(pid)
|
|
110
|
+
for g in groups:
|
|
111
|
+
for sig in (signal.SIGHUP, signal.SIGCONT):
|
|
112
|
+
try:
|
|
113
|
+
os.killpg(g, sig)
|
|
114
|
+
except OSError:
|
|
115
|
+
pass
|
|
116
|
+
deadline = time.monotonic() + GRACE
|
|
117
|
+
while groups and time.monotonic() < deadline:
|
|
118
|
+
reap(pid)
|
|
119
|
+
time.sleep(0.05)
|
|
120
|
+
groups = {g for g in groups if alive(g)}
|
|
121
|
+
for g in groups | groups_of(pid):
|
|
122
|
+
try:
|
|
123
|
+
os.killpg(g, signal.SIGKILL)
|
|
124
|
+
except OSError:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def alive(group):
|
|
129
|
+
try:
|
|
130
|
+
os.killpg(group, 0)
|
|
131
|
+
return True
|
|
132
|
+
except OSError:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
status = {"code": None}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def reap(pid):
|
|
140
|
+
"""PROGRAM's exit code once it has exited (and been waited for), else None."""
|
|
141
|
+
if status["code"] is None:
|
|
142
|
+
try:
|
|
143
|
+
done, raw = os.waitpid(pid, os.WNOHANG)
|
|
144
|
+
except ChildProcessError:
|
|
145
|
+
done, raw = pid, 0
|
|
146
|
+
if done:
|
|
147
|
+
status["code"] = 128 + os.WTERMSIG(raw) if os.WIFSIGNALED(raw) else os.WEXITSTATUS(raw)
|
|
148
|
+
return status["code"]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main():
|
|
152
|
+
cols, rows, program, argv = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3], sys.argv[4:]
|
|
153
|
+
control_open = True
|
|
154
|
+
try:
|
|
155
|
+
os.fstat(CONTROL)
|
|
156
|
+
except OSError:
|
|
157
|
+
control_open = False # started by hand: no control channel
|
|
158
|
+
pid, master, tty = start(program, argv, cols, rows, control_open)
|
|
159
|
+
os.set_blocking(master, False)
|
|
160
|
+
|
|
161
|
+
def tell(message):
|
|
162
|
+
if control_open:
|
|
163
|
+
try:
|
|
164
|
+
write_all(CONTROL, (json.dumps(message) + "\n").encode())
|
|
165
|
+
except OSError:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
tell({"pid": pid, "tty": tty})
|
|
169
|
+
try:
|
|
170
|
+
relay(pid, master, tell, [0, CONTROL] if control_open else [0])
|
|
171
|
+
except OSError:
|
|
172
|
+
pass # code-flow went away mid-write: hang up all the same
|
|
173
|
+
finally:
|
|
174
|
+
if status["code"] is not None:
|
|
175
|
+
# PROGRAM exited: pass on what it printed last before hanging up.
|
|
176
|
+
deadline = time.monotonic() + 0.3
|
|
177
|
+
while time.monotonic() < deadline:
|
|
178
|
+
try:
|
|
179
|
+
if not select.select([master], [], [], 0.05)[0]:
|
|
180
|
+
break
|
|
181
|
+
data = os.read(master, 65536)
|
|
182
|
+
if not data:
|
|
183
|
+
break
|
|
184
|
+
write_all(1, data)
|
|
185
|
+
except OSError:
|
|
186
|
+
break
|
|
187
|
+
hang_up(pid)
|
|
188
|
+
os.close(master)
|
|
189
|
+
deadline = time.monotonic() + GRACE
|
|
190
|
+
while reap(pid) is None and time.monotonic() < deadline:
|
|
191
|
+
time.sleep(0.05)
|
|
192
|
+
code = status["code"]
|
|
193
|
+
sys.exit(code if code is not None else 129)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def relay(pid, master, tell, inputs):
|
|
197
|
+
"""Bytes both ways until PROGRAM exits, the terminal closes, a stop signal, or code-flow goes away."""
|
|
198
|
+
# Signals only wake the loop; it decides what to do.
|
|
199
|
+
wake_r, wake_w = os.pipe()
|
|
200
|
+
os.set_blocking(wake_r, False)
|
|
201
|
+
os.set_blocking(wake_w, False)
|
|
202
|
+
signal.set_wakeup_fd(wake_w)
|
|
203
|
+
stop = {"now": False}
|
|
204
|
+
|
|
205
|
+
def on_stop(*_):
|
|
206
|
+
stop["now"] = True
|
|
207
|
+
|
|
208
|
+
for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
|
|
209
|
+
signal.signal(sig, on_stop)
|
|
210
|
+
signal.signal(signal.SIGCHLD, lambda *_: None)
|
|
211
|
+
|
|
212
|
+
keys = bytearray() # keystrokes the terminal hasn't taken yet
|
|
213
|
+
control = b""
|
|
214
|
+
fg = None
|
|
215
|
+
while not stop["now"]:
|
|
216
|
+
readable, writable, _ = select.select([master, wake_r] + inputs, [master] if keys else [], [], 0.5)
|
|
217
|
+
if wake_r in readable:
|
|
218
|
+
try:
|
|
219
|
+
os.read(wake_r, 512)
|
|
220
|
+
except OSError:
|
|
221
|
+
pass
|
|
222
|
+
if master in readable:
|
|
223
|
+
try:
|
|
224
|
+
data = os.read(master, 65536)
|
|
225
|
+
except BlockingIOError:
|
|
226
|
+
data = None
|
|
227
|
+
except OSError as err: # EIO on Linux once nothing holds the terminal open
|
|
228
|
+
if err.errno != errno.EIO:
|
|
229
|
+
raise
|
|
230
|
+
data = b""
|
|
231
|
+
if data:
|
|
232
|
+
write_all(1, data)
|
|
233
|
+
elif data == b"":
|
|
234
|
+
break
|
|
235
|
+
if 0 in readable:
|
|
236
|
+
data = os.read(0, 65536)
|
|
237
|
+
if not data:
|
|
238
|
+
break # code-flow went away
|
|
239
|
+
keys += data
|
|
240
|
+
if keys:
|
|
241
|
+
try:
|
|
242
|
+
del keys[:os.write(master, keys)]
|
|
243
|
+
except BlockingIOError:
|
|
244
|
+
pass
|
|
245
|
+
if CONTROL in readable:
|
|
246
|
+
try:
|
|
247
|
+
data = os.read(CONTROL, 65536)
|
|
248
|
+
except BlockingIOError:
|
|
249
|
+
data = None
|
|
250
|
+
if data == b"":
|
|
251
|
+
inputs.remove(CONTROL)
|
|
252
|
+
elif data:
|
|
253
|
+
control += data
|
|
254
|
+
*lines, control = control.split(b"\n")
|
|
255
|
+
for line in lines:
|
|
256
|
+
try:
|
|
257
|
+
size = json.loads(line.decode()).get("resize")
|
|
258
|
+
set_size(master, max(2, int(size[0])), max(1, int(size[1])))
|
|
259
|
+
except (ValueError, TypeError, IndexError, AttributeError, OSError):
|
|
260
|
+
pass
|
|
261
|
+
if reap(pid) is not None:
|
|
262
|
+
break
|
|
263
|
+
try:
|
|
264
|
+
now = os.tcgetpgrp(master)
|
|
265
|
+
except OSError:
|
|
266
|
+
now = None
|
|
267
|
+
if now != fg:
|
|
268
|
+
fg = now
|
|
269
|
+
tell({"fg": fg})
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
main()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { mkdir, open, rename, unlink } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, join } from "node:path";
|
|
5
|
+
import { CODEFLOW_HOME } from "./config.js";
|
|
6
|
+
const liveBindings = /* @__PURE__ */ new Map();
|
|
7
|
+
const fingerprint = (connection) => createHash("sha256").update(JSON.stringify([connection.id, connection.command, connection.args])).digest("hex");
|
|
8
|
+
function acpBindings(roles) {
|
|
9
|
+
return [...new Set(Object.values(roles).flat().filter((id) => id.startsWith("acp:")))].sort().map((id) => {
|
|
10
|
+
const binding = liveBindings.get(id);
|
|
11
|
+
if (!binding) throw new Error(`ACP connection ${id} is disabled or missing. Restore it in Settings \u2192 Agents & models.`);
|
|
12
|
+
return { ...binding };
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function assertAcpBindings(run) {
|
|
16
|
+
if (JSON.stringify(acpBindings(run.roles)) !== JSON.stringify(run.acpConnections ?? [])) throw new Error("This run's ACP command or arguments changed. Restore its original connection or start a new run.");
|
|
17
|
+
}
|
|
18
|
+
const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
19
|
+
const text = (value, limit) => typeof value === "string" && value.length > 0 && value.length <= limit && !/[\x00-\x1f\x7f]/.test(value);
|
|
20
|
+
function validateAcpConnection(value) {
|
|
21
|
+
if (!object(value) || Object.keys(value).some((key) => !["id", "name", "command", "args", "enabled"].includes(key)) || typeof value.id !== "string" || !/^acp:[a-z][a-z0-9-]{0,40}$/.test(value.id) || !text(value.name, 100) || !value.name.trim() || !text(value.command, 4e3) || !(isAbsolute(value.command) || /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/.test(value.command)) || !Array.isArray(value.args) || value.args.length > 32 || value.args.some((arg) => typeof arg !== "string" || arg.length > 4e3 || /[\x00-\x1f\x7f]/.test(arg)) || typeof value.enabled !== "boolean") {
|
|
22
|
+
throw new Error("Use a unique connection ID, a name, an executable name or absolute path, and at most 32 arguments. Shell command strings are not supported.");
|
|
23
|
+
}
|
|
24
|
+
return { id: value.id, name: value.name.trim(), command: value.command, args: [...value.args], enabled: value.enabled };
|
|
25
|
+
}
|
|
26
|
+
class AcpConnections {
|
|
27
|
+
constructor(publish, home = CODEFLOW_HOME) {
|
|
28
|
+
this.publish = publish;
|
|
29
|
+
this.home = home;
|
|
30
|
+
}
|
|
31
|
+
document = { version: 1, revision: 0, connections: [] };
|
|
32
|
+
queue = Promise.resolve();
|
|
33
|
+
ready = false;
|
|
34
|
+
snapshot() {
|
|
35
|
+
return structuredClone(this.document);
|
|
36
|
+
}
|
|
37
|
+
activate(document) {
|
|
38
|
+
this.publish(document.connections.filter((connection) => connection.enabled));
|
|
39
|
+
liveBindings.clear();
|
|
40
|
+
for (const connection of document.connections.filter((connection2) => connection2.enabled)) liveBindings.set(connection.id, { id: connection.id, fingerprint: fingerprint(connection) });
|
|
41
|
+
this.document = document;
|
|
42
|
+
}
|
|
43
|
+
async load() {
|
|
44
|
+
try {
|
|
45
|
+
const file = await open(join(this.home, "acp-connections.json"), constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
const info = await file.stat();
|
|
49
|
+
if (!info.isFile() || info.size > 256 * 1024) throw new Error("ACP connections must be a regular file under 256 KiB");
|
|
50
|
+
const buffer = Buffer.alloc(256 * 1024 + 1);
|
|
51
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
52
|
+
if (bytesRead > 256 * 1024) throw new Error("ACP connections exceed 256 KiB");
|
|
53
|
+
raw = JSON.parse(buffer.subarray(0, bytesRead).toString("utf8"));
|
|
54
|
+
} finally {
|
|
55
|
+
await file.close();
|
|
56
|
+
}
|
|
57
|
+
if (!object(raw) || raw.version !== 1 || !Number.isSafeInteger(raw.revision) || raw.revision < 1 || !Array.isArray(raw.connections) || raw.connections.length > 16) throw new Error("Invalid ACP connection settings");
|
|
58
|
+
const connections = raw.connections.map(validateAcpConnection);
|
|
59
|
+
if (new Set(connections.map((connection) => connection.id)).size !== connections.length) throw new Error("Duplicate ACP connection IDs");
|
|
60
|
+
this.activate({ version: 1, revision: raw.revision, connections });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error.code !== "ENOENT") throw error;
|
|
63
|
+
this.activate(this.document);
|
|
64
|
+
}
|
|
65
|
+
this.ready = true;
|
|
66
|
+
}
|
|
67
|
+
save(revision, value) {
|
|
68
|
+
const connection = validateAcpConnection(value);
|
|
69
|
+
return this.change(revision, (connections) => [...connections.filter((item) => item.id !== connection.id), connection]);
|
|
70
|
+
}
|
|
71
|
+
remove(revision, id) {
|
|
72
|
+
return this.change(revision, (connections) => {
|
|
73
|
+
if (!connections.some((connection) => connection.id === id)) throw new Error("ACP connection no longer exists");
|
|
74
|
+
return connections.filter((connection) => connection.id !== id);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
change(revision, update) {
|
|
78
|
+
const work = this.queue.then(async () => {
|
|
79
|
+
if (!this.ready) throw new Error("Repair ACP connection storage and restart before changing connections");
|
|
80
|
+
if (revision !== this.document.revision) throw new Error("ACP connections changed in another window. Reload connections before saving.");
|
|
81
|
+
const next = { version: 1, revision: this.document.revision + 1, connections: update(this.snapshot().connections) };
|
|
82
|
+
const json = JSON.stringify(next, null, 2);
|
|
83
|
+
if (next.connections.length > 16 || Buffer.byteLength(json) > 256 * 1024) throw new Error("Keep at most 16 ACP connections within 256 KiB");
|
|
84
|
+
await mkdir(this.home, { recursive: true, mode: 448 });
|
|
85
|
+
const temporary = join(this.home, `.acp-connections-${randomUUID()}.tmp`);
|
|
86
|
+
try {
|
|
87
|
+
const file = await open(temporary, "wx", 384);
|
|
88
|
+
try {
|
|
89
|
+
await file.writeFile(json);
|
|
90
|
+
await file.sync();
|
|
91
|
+
} finally {
|
|
92
|
+
await file.close();
|
|
93
|
+
}
|
|
94
|
+
await rename(temporary, join(this.home, "acp-connections.json"));
|
|
95
|
+
} finally {
|
|
96
|
+
await unlink(temporary).catch(() => void 0);
|
|
97
|
+
}
|
|
98
|
+
this.activate(next);
|
|
99
|
+
return this.snapshot();
|
|
100
|
+
});
|
|
101
|
+
this.queue = work.catch(() => void 0);
|
|
102
|
+
return work;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
AcpConnections,
|
|
107
|
+
acpBindings,
|
|
108
|
+
assertAcpBindings,
|
|
109
|
+
validateAcpConnection
|
|
110
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const acpPresets = [
|
|
2
|
+
{
|
|
3
|
+
key: "opencode",
|
|
4
|
+
name: "OpenCode",
|
|
5
|
+
command: "opencode",
|
|
6
|
+
args: ["acp"],
|
|
7
|
+
install: "npm install -g opencode-ai",
|
|
8
|
+
login: "opencode auth login",
|
|
9
|
+
docs: "https://opencode.ai/docs/acp/"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
key: "kilo",
|
|
13
|
+
name: "Kilo",
|
|
14
|
+
command: "kilo",
|
|
15
|
+
args: ["acp"],
|
|
16
|
+
install: "npm install -g @kilocode/cli",
|
|
17
|
+
login: "kilo auth login",
|
|
18
|
+
docs: "https://kilo.ai/docs/code-with-ai/platforms/cli-reference"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: "cline",
|
|
22
|
+
name: "Cline",
|
|
23
|
+
command: "cline",
|
|
24
|
+
args: ["--acp"],
|
|
25
|
+
install: "npm install -g cline",
|
|
26
|
+
login: "cline auth",
|
|
27
|
+
docs: "https://docs.cline.bot/usage/acp",
|
|
28
|
+
help: "Cline asks before edits and commands by default. Enable Ask me for tool permissions in the run\u2019s permissions controls to approve these requests."
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
function acpPresetConnection(preset) {
|
|
32
|
+
return { id: `acp:${preset.key}`, name: preset.name, command: preset.command, args: [...preset.args], enabled: true };
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
acpPresetConnection,
|
|
36
|
+
acpPresets
|
|
37
|
+
};
|
|
File without changes
|