loopable-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/_connectorId-CDCuSgpN.js +1 -0
- package/.output/public/assets/_loopId-DOnIsVTW.js +1 -0
- package/.output/public/assets/_taskId-BO5MZnlZ.js +1 -0
- package/.output/public/assets/agents-CBozi7oi.js +1 -0
- package/.output/public/assets/alert-Bn_wjgTX.js +1 -0
- package/.output/public/assets/button-K3ypIhAZ.js +1 -0
- package/.output/public/assets/card-DiVnaTIE.js +1 -0
- package/.output/public/assets/chevron-right-BrctLtFK.js +1 -0
- package/.output/public/assets/connection-status-DKWelvLO.js +1 -0
- package/.output/public/assets/connector-icon-CEbdE71G.js +1 -0
- package/.output/public/assets/connectors-JyX3IzJj.js +1 -0
- package/.output/public/assets/gaps-PjmcbaWo.js +1 -0
- package/.output/public/assets/inbox-CujRR0Cz.js +1 -0
- package/.output/public/assets/index-C9SQ_Baq.js +16 -0
- package/.output/public/assets/loop-form-Dw3cuTzw.js +1 -0
- package/.output/public/assets/loops-DcEfZa2_.js +1 -0
- package/.output/public/assets/manifests-yJpevyye.js +1 -0
- package/.output/public/assets/new-CwsnNuRW.js +1 -0
- package/.output/public/assets/page-bvnfRMum.js +1 -0
- package/.output/public/assets/play-yHLs4DjK.js +1 -0
- package/.output/public/assets/plus-Ber25lrU.js +1 -0
- package/.output/public/assets/preload-helper-BoU_7EQC.js +3 -0
- package/.output/public/assets/{refresh-cw-C3xBnKfy.js → refresh-cw-BzSY7M8I.js} +1 -1
- package/.output/public/assets/rotate-ccw-DLv9EfCX.js +1 -0
- package/.output/public/assets/runners-DzX-RU3i.js +2 -0
- package/.output/public/assets/select-C9grIFwG.js +1 -0
- package/.output/public/assets/setting-fields-DUc3npgh.js +3 -0
- package/.output/public/assets/styles-B9B98zLk.css +2 -0
- package/.output/public/assets/switch-DwABVI6d.js +1 -0
- package/.output/public/assets/task-list-Ddk8-qt7.js +1 -0
- package/.output/public/assets/textarea-Ds-gIYUQ.js +1 -0
- package/.output/public/assets/time-goBWd-5J.js +1 -0
- package/.output/public/assets/use-live-tasks-DZNNuECy.js +1 -0
- package/.output/public/assets/useCompositeListItem-P4Nmmn7j.js +1 -0
- package/.output/public/assets/usePositioner-B7i9Ejzh.js +1 -0
- package/.output/server/_chunks/ssr-renderer.mjs +1 -1
- package/.output/server/{_connectorId-C9yydJrC.mjs → _connectorId-COuQN02a.mjs} +198 -188
- package/.output/server/_libs/@base-ui/react+[...].mjs +1410 -804
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/lucide-react.mjs +93 -84
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_loopId-CZh6_Yhr.mjs +310 -0
- package/.output/server/_ssr/{agents-DG643yoJ.mjs → agents-B_qo1gpL.mjs} +195 -6
- package/.output/server/_ssr/{agents-C7B6RJ6-.mjs → agents-Bz10gz_m.mjs} +8 -8
- package/.output/server/_ssr/agents-CjbOUp3Z.mjs +206 -0
- package/.output/server/_ssr/{alert-DQvzE7bu.mjs → alert-C2WBWfzo.mjs} +2 -2
- package/.output/server/_ssr/card-r1cV-PCN.mjs +36 -0
- package/.output/server/_ssr/{client-BtVRVhQ2.mjs → client-Dk3SD8Il.mjs} +5 -1
- package/.output/server/_ssr/connection-status-BogAL-Fj.mjs +32 -0
- package/.output/server/_ssr/{connections-Df-aDpfJ.mjs → connections-Cqvb6Gw2.mjs} +372 -325
- package/.output/server/_ssr/connector-icon-DrhVbhs6.mjs +190 -0
- package/.output/server/_ssr/connectors-BfS5tQKr.mjs +81 -0
- package/.output/server/_ssr/{connectors-e0mf2pMs.mjs → connectors-DJI-rzPU.mjs} +10 -10
- package/.output/server/_ssr/{createServerRpc-DPDrnFdH.mjs → createServerRpc-DDrp2nLT.mjs} +2 -2
- package/.output/server/_ssr/{gaps-Cz76ZicM.mjs → gaps-CcvHiLcj.mjs} +15 -7
- package/.output/server/_ssr/inbox-D1ef761v.mjs +178 -0
- package/.output/server/_ssr/loop-form-DWwOR05R.mjs +346 -0
- package/.output/server/_ssr/{loops-D8WuK7Ke.mjs → loops-6WhUe_lc.mjs} +37 -27
- package/.output/server/_ssr/loops-DHPjzG-t.mjs +217 -0
- package/.output/server/_ssr/new-ChKBdcXJ.mjs +245 -0
- package/.output/server/_ssr/page-DZVxKvdn.mjs +120 -0
- package/.output/server/_ssr/{router-54IDWTVz.mjs → router-DdwREiOf.mjs} +162 -489
- package/.output/server/_ssr/runners-CK9Cx9fS.mjs +217 -0
- package/.output/server/_ssr/runners-CrznFPHt.mjs +140 -0
- package/.output/server/_ssr/{runners-Cj6z9Mqh.mjs → runners-W-pgo5yo.mjs} +5 -5
- package/.output/server/_ssr/{secrets-CRnpwGBh.mjs → secrets-CV5scFlt.mjs} +2 -2
- package/.output/server/_ssr/select-BtndHhHb.mjs +76 -0
- package/.output/server/_ssr/{setting-fields-DzBlslan.mjs → setting-fields-LNgywAW7.mjs} +25 -20
- package/.output/server/_ssr/ssr.mjs +43 -42
- package/.output/server/_ssr/{start-Vq3Oan3v.mjs → start-C_j-e4vF.mjs} +9 -4
- package/.output/server/_ssr/{switch-1pnd_bPJ.mjs → switch-odxNO_S4.mjs} +3 -3
- package/.output/server/_ssr/task-list-Dnmq_WeE.mjs +150 -0
- package/.output/server/_ssr/{tasks-Qtml5TlK.mjs → tasks-D-kXLgVP.mjs} +13 -13
- package/.output/server/_ssr/{tasks-BDqznm57.mjs → tasks-D7aUvA33.mjs} +44 -17
- package/.output/server/_ssr/{textarea-CHJTKtsJ.mjs → textarea-D2c_vTIC.mjs} +2 -2
- package/.output/server/_ssr/time-Cy8TZP0Z.mjs +13 -0
- package/.output/server/_ssr/use-live-tasks-BzhTkEOL.mjs +40 -0
- package/.output/server/_tanstack-start-manifest_v-2HUdn-2E.mjs +166 -0
- package/.output/server/_taskId-D3KkNJW1.mjs +263 -0
- package/.output/server/index.mjs +199 -164
- package/README.md +36 -6
- package/dist/cli.mjs +161 -8
- package/dist/dispatcher.mjs +671 -285
- package/dist/runner.mjs +333 -46
- package/drizzle/0025_prompt_tasks_done.sql +2 -0
- package/drizzle/0026_guidance_into_prompt.sql +3 -0
- package/drizzle/0027_relative_folders.sql +5 -0
- package/drizzle/0028_runner_version.sql +5 -0
- package/drizzle/meta/_journal.json +29 -1
- package/package.json +4 -2
- package/.output/public/assets/_connectorId-CkygNLny.js +0 -1
- package/.output/public/assets/_loopId-Cshlc5Ja.js +0 -1
- package/.output/public/assets/_taskId-BKDIJ_Fg.js +0 -1
- package/.output/public/assets/alert-BT2EVMJP.js +0 -1
- package/.output/public/assets/arrow-left-CWUZt6k2.js +0 -1
- package/.output/public/assets/arrow-right-dy_oOg_c.js +0 -1
- package/.output/public/assets/connection-status-DCqqmY_6.js +0 -1
- package/.output/public/assets/connector-icon-BUoK6Cru.js +0 -1
- package/.output/public/assets/connectors-CeLBtEtg.js +0 -1
- package/.output/public/assets/external-link-DC11orFA.js +0 -1
- package/.output/public/assets/gaps-BtFoD7mF.js +0 -1
- package/.output/public/assets/inbox-BYNjkNDJ.js +0 -1
- package/.output/public/assets/index-0yP3Jn_G.js +0 -18
- package/.output/public/assets/loader-B4Epchm_.js +0 -1
- package/.output/public/assets/loop-form-DnSJNc1k.js +0 -1
- package/.output/public/assets/loops-1CvbqO6m.js +0 -1
- package/.output/public/assets/new-m5NlqYQT.js +0 -1
- package/.output/public/assets/play-DD4xQgvi.js +0 -1
- package/.output/public/assets/preload-helper-CfdAYKD7.js +0 -3
- package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +0 -1
- package/.output/public/assets/runners-D8QVl-Y6.js +0 -1
- package/.output/public/assets/select-Cppu-hti.js +0 -1
- package/.output/public/assets/setting-fields-bJ_gTG7Q.js +0 -3
- package/.output/public/assets/styles-ClTGaXU0.css +0 -2
- package/.output/public/assets/switch-hRIbNGOj.js +0 -1
- package/.output/public/assets/task-list-BAxjcVgr.js +0 -1
- package/.output/public/assets/textarea-Fyuzi6Vp.js +0 -1
- package/.output/public/assets/trash-2-BXZzBsoV.js +0 -1
- package/.output/public/assets/useValueChanged-VVyq4hb9.js +0 -1
- package/.output/server/_loopId-B0VG-eBC.mjs +0 -301
- package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +0 -31
- package/.output/server/_ssr/connector-icon-WvxwZ5TF.mjs +0 -56
- package/.output/server/_ssr/connectors-BU0IiRge.mjs +0 -107
- package/.output/server/_ssr/inbox-CAUBa1I3.mjs +0 -114
- package/.output/server/_ssr/loop-form-FqSaXiHl.mjs +0 -413
- package/.output/server/_ssr/loops-Dqoebe8q.mjs +0 -222
- package/.output/server/_ssr/new-bBsVaWD6.mjs +0 -169
- package/.output/server/_ssr/runners-D2UAdLyj.mjs +0 -106
- package/.output/server/_ssr/runners-DGWaVvWi.mjs +0 -116
- package/.output/server/_ssr/task-list-_qgBS9LU.mjs +0 -187
- package/.output/server/_tanstack-start-manifest_v-CvheE7ZF.mjs +0 -131
- package/.output/server/_taskId-V4QLBO2N.mjs +0 -256
package/dist/dispatcher.mjs
CHANGED
|
@@ -55,9 +55,32 @@ var init_define = __esm({
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
// src/agents/claude/manifest.ts
|
|
59
|
+
var claudeManifest;
|
|
60
|
+
var init_manifest = __esm({
|
|
61
|
+
"src/agents/claude/manifest.ts"() {
|
|
62
|
+
"use strict";
|
|
63
|
+
init_define();
|
|
64
|
+
claudeManifest = defineAgentManifest({
|
|
65
|
+
id: "claude",
|
|
66
|
+
name: "Claude Code",
|
|
67
|
+
tagline: "Anthropic's coding agent, driven through its print mode.",
|
|
68
|
+
docsUrl: "https://docs.claude.com/en/docs/claude-code/cli-reference",
|
|
69
|
+
icon: "ClaudeCode",
|
|
70
|
+
accent: "bg-[#d97757] text-white",
|
|
71
|
+
binaries: ["claude"],
|
|
72
|
+
installHint: "Install with curl -fsSL https://claude.ai/install.sh | bash, then run claude auth login.",
|
|
73
|
+
permissionModes: ["read_only", "workspace_write"],
|
|
74
|
+
supportsModel: true,
|
|
75
|
+
modelPlaceholder: "e.g. sonnet or opus; leave empty for the default",
|
|
76
|
+
defaults: { permissionMode: "read_only", model: null, timeoutMs: 3e5 }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
58
81
|
// src/agents/codex/manifest.ts
|
|
59
82
|
var codexManifest;
|
|
60
|
-
var
|
|
83
|
+
var init_manifest2 = __esm({
|
|
61
84
|
"src/agents/codex/manifest.ts"() {
|
|
62
85
|
"use strict";
|
|
63
86
|
init_define();
|
|
@@ -66,7 +89,7 @@ var init_manifest = __esm({
|
|
|
66
89
|
name: "Codex",
|
|
67
90
|
tagline: "OpenAI's coding agent, driven non-interactively through codex exec.",
|
|
68
91
|
docsUrl: "https://developers.openai.com/codex/cli",
|
|
69
|
-
icon: "
|
|
92
|
+
icon: "Codex",
|
|
70
93
|
accent: "bg-neutral-900 text-white",
|
|
71
94
|
binaries: ["codex"],
|
|
72
95
|
installHint: "Install with npm i -g @openai/codex, then run codex login.",
|
|
@@ -80,7 +103,7 @@ var init_manifest = __esm({
|
|
|
80
103
|
|
|
81
104
|
// src/agents/cursor-agent/manifest.ts
|
|
82
105
|
var cursorAgentManifest;
|
|
83
|
-
var
|
|
106
|
+
var init_manifest3 = __esm({
|
|
84
107
|
"src/agents/cursor-agent/manifest.ts"() {
|
|
85
108
|
"use strict";
|
|
86
109
|
init_define();
|
|
@@ -89,7 +112,7 @@ var init_manifest2 = __esm({
|
|
|
89
112
|
name: "Cursor Agent",
|
|
90
113
|
tagline: "Cursor's CLI agent, driven through its print mode.",
|
|
91
114
|
docsUrl: "https://cursor.com/docs/cli",
|
|
92
|
-
icon: "
|
|
115
|
+
icon: "Cursor",
|
|
93
116
|
accent: "bg-blue-600 text-white",
|
|
94
117
|
binaries: ["cursor-agent"],
|
|
95
118
|
installHint: "Install with curl https://cursor.com/install -fsS | bash, then run cursor-agent login.",
|
|
@@ -101,11 +124,148 @@ var init_manifest2 = __esm({
|
|
|
101
124
|
}
|
|
102
125
|
});
|
|
103
126
|
|
|
127
|
+
// package.json
|
|
128
|
+
var package_default;
|
|
129
|
+
var init_package = __esm({
|
|
130
|
+
"package.json"() {
|
|
131
|
+
package_default = {
|
|
132
|
+
name: "loopable-cli",
|
|
133
|
+
version: "0.2.1",
|
|
134
|
+
description: "CLI to run Loopable: App, Dispatcher, and Runner.",
|
|
135
|
+
license: "MIT",
|
|
136
|
+
type: "module",
|
|
137
|
+
bin: {
|
|
138
|
+
loopable: "./dist/cli.mjs"
|
|
139
|
+
},
|
|
140
|
+
files: [
|
|
141
|
+
".output",
|
|
142
|
+
"dist",
|
|
143
|
+
"drizzle"
|
|
144
|
+
],
|
|
145
|
+
engines: {
|
|
146
|
+
node: ">=22"
|
|
147
|
+
},
|
|
148
|
+
packageManager: "pnpm@11.22.0",
|
|
149
|
+
repository: {
|
|
150
|
+
type: "git",
|
|
151
|
+
url: "git+https://github.com/imsobear/loopable.git"
|
|
152
|
+
},
|
|
153
|
+
bugs: {
|
|
154
|
+
url: "https://github.com/imsobear/loopable/issues"
|
|
155
|
+
},
|
|
156
|
+
homepage: "https://github.com/imsobear/loopable#readme",
|
|
157
|
+
publishConfig: {
|
|
158
|
+
access: "public"
|
|
159
|
+
},
|
|
160
|
+
imports: {
|
|
161
|
+
"#/*": "./src/*"
|
|
162
|
+
},
|
|
163
|
+
scripts: {
|
|
164
|
+
dev: "node --experimental-strip-types scripts/dev.ts",
|
|
165
|
+
start: "node dist/cli.mjs start",
|
|
166
|
+
dispatcher: "LOOPABLE_DEV=1 node --experimental-strip-types src/cli.ts dispatcher",
|
|
167
|
+
runner: "LOOPABLE_DEV=1 node --experimental-strip-types src/cli.ts runner",
|
|
168
|
+
"generate-routes": "tsr generate",
|
|
169
|
+
build: "vite build && node scripts/build-cli.mjs",
|
|
170
|
+
preview: "vite preview",
|
|
171
|
+
test: "vitest run",
|
|
172
|
+
"test:watch": "vitest",
|
|
173
|
+
"db:generate": "drizzle-kit generate",
|
|
174
|
+
"db:studio": "LOOPABLE_DEV=1 drizzle-kit studio",
|
|
175
|
+
typecheck: "tsc --noEmit",
|
|
176
|
+
release: "node scripts/release.mjs",
|
|
177
|
+
deploy: "LOOPABLE_CF=1 vite build && wrangler deploy",
|
|
178
|
+
"site:dev": "pnpm --filter loopable-site dev",
|
|
179
|
+
"site:build": "pnpm --filter loopable-site build",
|
|
180
|
+
"site:deploy": "pnpm --filter loopable-site deploy",
|
|
181
|
+
prepublishOnly: "pnpm typecheck && pnpm test && pnpm build"
|
|
182
|
+
},
|
|
183
|
+
dependencies: {
|
|
184
|
+
"@libsql/client": "^0.18.0"
|
|
185
|
+
},
|
|
186
|
+
devDependencies: {
|
|
187
|
+
"@base-ui/react": "^1.7.0",
|
|
188
|
+
"@cloudflare/containers": "^0.3.7",
|
|
189
|
+
"@cloudflare/vite-plugin": "^1.54.10",
|
|
190
|
+
"@fontsource-variable/geist": "^5.3.0",
|
|
191
|
+
"@tailwindcss/vite": "4.1.18",
|
|
192
|
+
"@tanstack/devtools-vite": "0.8.5",
|
|
193
|
+
"@tanstack/react-devtools": "0.10.12",
|
|
194
|
+
"@tanstack/react-router": "1.170.32",
|
|
195
|
+
"@tanstack/react-router-devtools": "1.167.1",
|
|
196
|
+
"@tanstack/react-start": "1.168.49",
|
|
197
|
+
"@tanstack/router-cli": "1.167.33",
|
|
198
|
+
"@types/node": "^22.10.2",
|
|
199
|
+
"@types/qrcode": "^1.5.6",
|
|
200
|
+
"@types/react": "^19.2.0",
|
|
201
|
+
"@types/react-dom": "^19.2.0",
|
|
202
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
203
|
+
"class-variance-authority": "^0.7.1",
|
|
204
|
+
clsx: "^2.1.1",
|
|
205
|
+
"drizzle-kit": "^0.31.10",
|
|
206
|
+
"drizzle-orm": "^0.45.2",
|
|
207
|
+
esbuild: "^0.28.2",
|
|
208
|
+
"lucide-react": "^1.39.0",
|
|
209
|
+
"next-themes": "^0.4.6",
|
|
210
|
+
nitro: "3.0.260610-beta",
|
|
211
|
+
qrcode: "^1.5.4",
|
|
212
|
+
react: "19.2.0",
|
|
213
|
+
"react-dom": "19.2.0",
|
|
214
|
+
shadcn: "^4.19.1",
|
|
215
|
+
sonner: "^2.0.8",
|
|
216
|
+
"tailwind-merge": "^3.6.0",
|
|
217
|
+
tailwindcss: "4.1.18",
|
|
218
|
+
"tw-animate-css": "^1.4.0",
|
|
219
|
+
typescript: "^6.0.2",
|
|
220
|
+
vite: "^8.0.0",
|
|
221
|
+
vitest: "^4.1.11",
|
|
222
|
+
wrangler: "^4.132.0"
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// src/lib/version.ts
|
|
229
|
+
function olderThan(a, b) {
|
|
230
|
+
if (!a) return true;
|
|
231
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10));
|
|
232
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10));
|
|
233
|
+
for (let i = 0; i < 3; i++) {
|
|
234
|
+
const x = pa[i] ?? 0;
|
|
235
|
+
const y = pb[i] ?? 0;
|
|
236
|
+
if (Number.isNaN(x)) return true;
|
|
237
|
+
if (x !== y) return x < y;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
var VERSION, RUNNER_PROTOCOL, LEGACY_RUNNER_PROTOCOL, FOLDER_JOB_PROTOCOL;
|
|
242
|
+
var init_version = __esm({
|
|
243
|
+
"src/lib/version.ts"() {
|
|
244
|
+
init_package();
|
|
245
|
+
VERSION = package_default.version;
|
|
246
|
+
RUNNER_PROTOCOL = 2;
|
|
247
|
+
LEGACY_RUNNER_PROTOCOL = 1;
|
|
248
|
+
FOLDER_JOB_PROTOCOL = 2;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// src/server/agent-job.ts
|
|
253
|
+
import { isAbsolute, join as join5 } from "node:path";
|
|
254
|
+
function folderIsLegacy(folder) {
|
|
255
|
+
return isAbsolute(folder.trim());
|
|
256
|
+
}
|
|
257
|
+
var init_agent_job = __esm({
|
|
258
|
+
"src/server/agent-job.ts"() {
|
|
259
|
+
"use strict";
|
|
260
|
+
init_version();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
104
264
|
// src/agents/discover.ts
|
|
105
265
|
import { execFile } from "node:child_process";
|
|
106
266
|
import { accessSync, constants } from "node:fs";
|
|
107
267
|
import { homedir as homedir2 } from "node:os";
|
|
108
|
-
import { join as
|
|
268
|
+
import { join as join6 } from "node:path";
|
|
109
269
|
import { promisify } from "node:util";
|
|
110
270
|
function unique(values) {
|
|
111
271
|
return [...new Set(values.filter(Boolean))];
|
|
@@ -114,7 +274,7 @@ function inheritedDirs() {
|
|
|
114
274
|
const home = homedir2();
|
|
115
275
|
return unique([
|
|
116
276
|
...(process.env.PATH ?? "").split(":"),
|
|
117
|
-
...HOME_DIRS.map((dir) =>
|
|
277
|
+
...HOME_DIRS.map((dir) => join6(home, dir)),
|
|
118
278
|
...SYSTEM_DIRS
|
|
119
279
|
]);
|
|
120
280
|
}
|
|
@@ -132,7 +292,7 @@ async function loginShellDirs() {
|
|
|
132
292
|
function executableIn(dirs, names) {
|
|
133
293
|
for (const dir of dirs) {
|
|
134
294
|
for (const name of names) {
|
|
135
|
-
const candidate =
|
|
295
|
+
const candidate = join6(dir, name);
|
|
136
296
|
try {
|
|
137
297
|
accessSync(candidate, constants.X_OK);
|
|
138
298
|
return candidate;
|
|
@@ -148,9 +308,9 @@ async function resolveBinary(names) {
|
|
|
148
308
|
function clean(text3) {
|
|
149
309
|
return text3.replace(ANSI, "").trim();
|
|
150
310
|
}
|
|
151
|
-
async function probe(bin,
|
|
311
|
+
async function probe(bin, args4, timeoutMs = 1e4) {
|
|
152
312
|
try {
|
|
153
|
-
const { stdout, stderr } = await exec(bin,
|
|
313
|
+
const { stdout, stderr } = await exec(bin, args4, { timeout: timeoutMs });
|
|
154
314
|
return { ok: true, text: clean(stdout) || clean(stderr) };
|
|
155
315
|
} catch (error) {
|
|
156
316
|
const failure = error;
|
|
@@ -298,14 +458,171 @@ var init_process = __esm({
|
|
|
298
458
|
}
|
|
299
459
|
});
|
|
300
460
|
|
|
461
|
+
// src/agents/claude/events.ts
|
|
462
|
+
function parse(line) {
|
|
463
|
+
const trimmed = line.trim();
|
|
464
|
+
if (!trimmed.startsWith("{")) return null;
|
|
465
|
+
try {
|
|
466
|
+
return JSON.parse(trimmed);
|
|
467
|
+
} catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function blocks(event) {
|
|
472
|
+
return event?.type === "assistant" ? event.message?.content ?? [] : [];
|
|
473
|
+
}
|
|
474
|
+
function describeTool(block, cwd) {
|
|
475
|
+
const verb = (block.name ?? "a tool").toLowerCase();
|
|
476
|
+
const input = block.input ?? {};
|
|
477
|
+
const subject = input.file_path ?? input.path ?? input.command ?? input.pattern ?? input.url ?? input.query;
|
|
478
|
+
if (typeof subject !== "string") return verb;
|
|
479
|
+
const short = cwd && subject.startsWith(`${cwd}/`) ? subject.slice(cwd.length + 1) : subject;
|
|
480
|
+
return `${verb} ${short}`;
|
|
481
|
+
}
|
|
482
|
+
function describeEvent(line, cwd) {
|
|
483
|
+
const event = parse(line);
|
|
484
|
+
if (!event) return line.trim() === "" ? null : line;
|
|
485
|
+
switch (event.type) {
|
|
486
|
+
case "system":
|
|
487
|
+
if (event.subtype !== "init") return null;
|
|
488
|
+
return event.model ? `Started with ${event.model}.` : "Started.";
|
|
489
|
+
case "assistant": {
|
|
490
|
+
const tools = blocks(event).filter((block) => block.type === "tool_use").map((block) => `Running ${describeTool(block, cwd)}`);
|
|
491
|
+
return tools.length > 0 ? tools.join("\n") : null;
|
|
492
|
+
}
|
|
493
|
+
case "result": {
|
|
494
|
+
const seconds = event.duration_ms !== void 0 ? (event.duration_ms / 1e3).toFixed(0) : "?";
|
|
495
|
+
const tokens = event.usage?.output_tokens;
|
|
496
|
+
const spent = tokens === void 0 ? "" : `, ${tokens} tokens out`;
|
|
497
|
+
return event.is_error ? `Failed after ${seconds}s${spent}: ${event.result ?? event.subtype ?? "unknown error"}` : `Answered after ${seconds}s${spent}.`;
|
|
498
|
+
}
|
|
499
|
+
default:
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function answerFrom(stdout) {
|
|
504
|
+
const events = stdout.split("\n").map(parse);
|
|
505
|
+
const result = events.filter((event) => event?.type === "result").at(-1);
|
|
506
|
+
const failed = result?.is_error === true;
|
|
507
|
+
if (typeof result?.result === "string" && result.result.trim() !== "") {
|
|
508
|
+
return { text: result.result.trim(), failed };
|
|
509
|
+
}
|
|
510
|
+
const said = events.flatMap(blocks);
|
|
511
|
+
const lastTool = said.findLastIndex((block) => block.type === "tool_use");
|
|
512
|
+
const text3 = said.slice(lastTool + 1).filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n").trim();
|
|
513
|
+
return { text: text3, failed };
|
|
514
|
+
}
|
|
515
|
+
var init_events = __esm({
|
|
516
|
+
"src/agents/claude/events.ts"() {
|
|
517
|
+
"use strict";
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// src/agents/claude/runtime.ts
|
|
522
|
+
async function binary() {
|
|
523
|
+
const found = await resolveBinary(claudeManifest.binaries);
|
|
524
|
+
if (!found) throw new Error("The claude binary was not found on this host.");
|
|
525
|
+
return found;
|
|
526
|
+
}
|
|
527
|
+
function args(input) {
|
|
528
|
+
const list = [
|
|
529
|
+
"-p",
|
|
530
|
+
// A stream of events rather than one block at the end, so a run can be
|
|
531
|
+
// watched. Print mode refuses stream-json without --verbose.
|
|
532
|
+
"--output-format",
|
|
533
|
+
"stream-json",
|
|
534
|
+
"--verbose",
|
|
535
|
+
// Session files would outlive a run that is meant to leave nothing behind.
|
|
536
|
+
"--no-session-persistence"
|
|
537
|
+
];
|
|
538
|
+
if (input.settings.permissionMode === "read_only") {
|
|
539
|
+
list.push("--permission-mode", "dontAsk", "--tools", "Read,Grep,Glob");
|
|
540
|
+
} else {
|
|
541
|
+
list.push("--permission-mode", "acceptEdits", "--allowedTools", "Bash");
|
|
542
|
+
}
|
|
543
|
+
if (input.settings.model) list.push("--model", input.settings.model);
|
|
544
|
+
list.push("--", input.prompt);
|
|
545
|
+
return list;
|
|
546
|
+
}
|
|
547
|
+
var claudeRuntime;
|
|
548
|
+
var init_runtime = __esm({
|
|
549
|
+
"src/agents/claude/runtime.ts"() {
|
|
550
|
+
"use strict";
|
|
551
|
+
init_define();
|
|
552
|
+
init_discover();
|
|
553
|
+
init_process();
|
|
554
|
+
init_events();
|
|
555
|
+
init_manifest();
|
|
556
|
+
claudeRuntime = defineAgentRuntime({
|
|
557
|
+
async detect() {
|
|
558
|
+
const found = await resolveBinary(claudeManifest.binaries);
|
|
559
|
+
if (!found) return { installed: false };
|
|
560
|
+
const version2 = await probe(found, ["--version"]);
|
|
561
|
+
return { installed: true, binaryPath: found, version: firstLine(version2.text) };
|
|
562
|
+
},
|
|
563
|
+
async authStatus() {
|
|
564
|
+
const found = await resolveBinary(claudeManifest.binaries);
|
|
565
|
+
if (!found) return { signedIn: false, detail: "Not installed." };
|
|
566
|
+
const result = await probe(found, ["auth", "status", "--json"]);
|
|
567
|
+
try {
|
|
568
|
+
const status = JSON.parse(result.text);
|
|
569
|
+
return status.loggedIn ? { signedIn: true, detail: `Signed in (${status.authMethod ?? "unknown method"}).` } : { signedIn: false, detail: "Run claude auth login." };
|
|
570
|
+
} catch {
|
|
571
|
+
return {
|
|
572
|
+
signedIn: result.ok,
|
|
573
|
+
detail: firstLine(result.text) || (result.ok ? "Signed in." : "Run claude auth login.")
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
invocation(input) {
|
|
578
|
+
return { bin: claudeManifest.binaries[0], args: args(input) };
|
|
579
|
+
},
|
|
580
|
+
async run(input) {
|
|
581
|
+
const bin = await binary();
|
|
582
|
+
const argv = args(input);
|
|
583
|
+
const outcome = await runProcess({
|
|
584
|
+
bin,
|
|
585
|
+
args: argv,
|
|
586
|
+
cwd: input.cwd,
|
|
587
|
+
timeoutMs: input.settings.timeoutMs,
|
|
588
|
+
signal: input.signal,
|
|
589
|
+
logPath: input.logFile,
|
|
590
|
+
logLine: (line) => describeEvent(line, input.cwd)
|
|
591
|
+
});
|
|
592
|
+
const command = `${bin} ${argv.join(" ")}`;
|
|
593
|
+
const answer = answerFrom(outcome.stdout);
|
|
594
|
+
if (outcome.aborted) {
|
|
595
|
+
return { ok: false, aborted: true, output: answer.text, durationMs: outcome.durationMs, command };
|
|
596
|
+
}
|
|
597
|
+
if (outcome.timedOut) {
|
|
598
|
+
return {
|
|
599
|
+
ok: false,
|
|
600
|
+
output: answer.text,
|
|
601
|
+
detail: `Claude Code did not finish within ${Math.round(input.settings.timeoutMs / 1e3)}s.`,
|
|
602
|
+
durationMs: outcome.durationMs,
|
|
603
|
+
command
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
ok: outcome.code === 0 && !answer.failed,
|
|
608
|
+
output: answer.text,
|
|
609
|
+
detail: outcome.code === 0 && !answer.failed ? void 0 : answer.failed ? answer.text || "The agent reported an error." : outcome.stderr || `Exited with code ${outcome.code}.`,
|
|
610
|
+
durationMs: outcome.durationMs,
|
|
611
|
+
command
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
301
618
|
// src/agents/codex/runtime.ts
|
|
302
619
|
import { readFileSync as readFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
303
|
-
async function
|
|
620
|
+
async function binary2() {
|
|
304
621
|
const found = await resolveBinary(codexManifest.binaries);
|
|
305
622
|
if (!found) throw new Error("The codex binary was not found on this host.");
|
|
306
623
|
return found;
|
|
307
624
|
}
|
|
308
|
-
function
|
|
625
|
+
function args2(input) {
|
|
309
626
|
const list = [
|
|
310
627
|
"exec",
|
|
311
628
|
"--sandbox",
|
|
@@ -325,13 +642,13 @@ function args(input) {
|
|
|
325
642
|
return list;
|
|
326
643
|
}
|
|
327
644
|
var codexRuntime;
|
|
328
|
-
var
|
|
645
|
+
var init_runtime2 = __esm({
|
|
329
646
|
"src/agents/codex/runtime.ts"() {
|
|
330
647
|
"use strict";
|
|
331
648
|
init_define();
|
|
332
649
|
init_discover();
|
|
333
650
|
init_process();
|
|
334
|
-
|
|
651
|
+
init_manifest2();
|
|
335
652
|
codexRuntime = defineAgentRuntime({
|
|
336
653
|
async detect() {
|
|
337
654
|
const found = await resolveBinary(codexManifest.binaries);
|
|
@@ -349,11 +666,11 @@ var init_runtime = __esm({
|
|
|
349
666
|
};
|
|
350
667
|
},
|
|
351
668
|
invocation(input) {
|
|
352
|
-
return { bin: codexManifest.binaries[0], args:
|
|
669
|
+
return { bin: codexManifest.binaries[0], args: args2(input) };
|
|
353
670
|
},
|
|
354
671
|
async run(input) {
|
|
355
|
-
const bin = await
|
|
356
|
-
const argv =
|
|
672
|
+
const bin = await binary2();
|
|
673
|
+
const argv = args2(input);
|
|
357
674
|
const command = `${bin} ${argv.join(" ")}`;
|
|
358
675
|
const outcome = await runProcess({
|
|
359
676
|
bin,
|
|
@@ -398,7 +715,7 @@ var init_runtime = __esm({
|
|
|
398
715
|
});
|
|
399
716
|
|
|
400
717
|
// src/agents/cursor-agent/events.ts
|
|
401
|
-
function
|
|
718
|
+
function parse2(line) {
|
|
402
719
|
const trimmed = line.trim();
|
|
403
720
|
if (!trimmed.startsWith("{")) return null;
|
|
404
721
|
try {
|
|
@@ -410,24 +727,24 @@ function parse(line) {
|
|
|
410
727
|
function textOf3(event) {
|
|
411
728
|
return (event.message?.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
412
729
|
}
|
|
413
|
-
function
|
|
730
|
+
function describeTool2(call3, cwd) {
|
|
414
731
|
const [name, body] = Object.entries(call3 ?? {})[0] ?? [];
|
|
415
732
|
if (!name) return "a tool";
|
|
416
733
|
const verb = name.replace(/ToolCall$/, "").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
|
|
417
|
-
const
|
|
418
|
-
const subject =
|
|
734
|
+
const args4 = body?.args ?? {};
|
|
735
|
+
const subject = args4.path ?? args4.command ?? args4.query ?? args4.pattern;
|
|
419
736
|
if (typeof subject !== "string") return verb;
|
|
420
737
|
const short = cwd && subject.startsWith(`${cwd}/`) ? subject.slice(cwd.length + 1) : subject;
|
|
421
738
|
return `${verb} ${short}`;
|
|
422
739
|
}
|
|
423
|
-
function
|
|
424
|
-
const event =
|
|
740
|
+
function describeEvent2(line, cwd) {
|
|
741
|
+
const event = parse2(line);
|
|
425
742
|
if (!event) return line.trim() === "" ? null : line;
|
|
426
743
|
switch (event.type) {
|
|
427
744
|
case "system":
|
|
428
745
|
return event.model ? `Started with ${event.model}.` : "Started.";
|
|
429
746
|
case "tool_call":
|
|
430
|
-
return event.subtype === "started" ? `Running ${
|
|
747
|
+
return event.subtype === "started" ? `Running ${describeTool2(event.tool_call, cwd)}` : null;
|
|
431
748
|
case "assistant":
|
|
432
749
|
return null;
|
|
433
750
|
case "result": {
|
|
@@ -440,8 +757,8 @@ function describeEvent(line, cwd) {
|
|
|
440
757
|
return null;
|
|
441
758
|
}
|
|
442
759
|
}
|
|
443
|
-
function
|
|
444
|
-
const events = stdout.split("\n").map(
|
|
760
|
+
function answerFrom2(stdout) {
|
|
761
|
+
const events = stdout.split("\n").map(parse2);
|
|
445
762
|
const result = events.filter((event) => event?.type === "result").at(-1);
|
|
446
763
|
const failed = result?.is_error === true;
|
|
447
764
|
const whole = events.findLastIndex(
|
|
@@ -453,19 +770,19 @@ function answerFrom(stdout) {
|
|
|
453
770
|
if (partial.length > 0) return { text: partial.join("").trim(), failed };
|
|
454
771
|
return { text: (result?.result ?? "").trim(), failed };
|
|
455
772
|
}
|
|
456
|
-
var
|
|
773
|
+
var init_events2 = __esm({
|
|
457
774
|
"src/agents/cursor-agent/events.ts"() {
|
|
458
775
|
"use strict";
|
|
459
776
|
}
|
|
460
777
|
});
|
|
461
778
|
|
|
462
779
|
// src/agents/cursor-agent/runtime.ts
|
|
463
|
-
async function
|
|
780
|
+
async function binary3() {
|
|
464
781
|
const found = await resolveBinary(cursorAgentManifest.binaries);
|
|
465
782
|
if (!found) throw new Error("The cursor-agent binary was not found on this host.");
|
|
466
783
|
return found;
|
|
467
784
|
}
|
|
468
|
-
function
|
|
785
|
+
function args3(input) {
|
|
469
786
|
const list = [
|
|
470
787
|
"-p",
|
|
471
788
|
// A stream of events rather than one block at the end: it can be watched
|
|
@@ -490,14 +807,14 @@ function args2(input) {
|
|
|
490
807
|
return list;
|
|
491
808
|
}
|
|
492
809
|
var cursorAgentRuntime;
|
|
493
|
-
var
|
|
810
|
+
var init_runtime3 = __esm({
|
|
494
811
|
"src/agents/cursor-agent/runtime.ts"() {
|
|
495
812
|
"use strict";
|
|
496
813
|
init_define();
|
|
497
814
|
init_discover();
|
|
498
815
|
init_process();
|
|
499
|
-
|
|
500
|
-
|
|
816
|
+
init_events2();
|
|
817
|
+
init_manifest3();
|
|
501
818
|
cursorAgentRuntime = defineAgentRuntime({
|
|
502
819
|
async detect() {
|
|
503
820
|
const found = await resolveBinary(cursorAgentManifest.binaries);
|
|
@@ -516,11 +833,11 @@ var init_runtime2 = __esm({
|
|
|
516
833
|
};
|
|
517
834
|
},
|
|
518
835
|
invocation(input) {
|
|
519
|
-
return { bin: cursorAgentManifest.binaries[0], args:
|
|
836
|
+
return { bin: cursorAgentManifest.binaries[0], args: args3(input) };
|
|
520
837
|
},
|
|
521
838
|
async run(input) {
|
|
522
|
-
const bin = await
|
|
523
|
-
const argv =
|
|
839
|
+
const bin = await binary3();
|
|
840
|
+
const argv = args3(input);
|
|
524
841
|
const outcome = await runProcess({
|
|
525
842
|
bin,
|
|
526
843
|
args: argv,
|
|
@@ -528,10 +845,10 @@ var init_runtime2 = __esm({
|
|
|
528
845
|
timeoutMs: input.settings.timeoutMs,
|
|
529
846
|
signal: input.signal,
|
|
530
847
|
logPath: input.logFile,
|
|
531
|
-
logLine: (line) =>
|
|
848
|
+
logLine: (line) => describeEvent2(line, input.cwd)
|
|
532
849
|
});
|
|
533
850
|
const command = `${bin} ${argv.join(" ")}`;
|
|
534
|
-
const answer =
|
|
851
|
+
const answer = answerFrom2(outcome.stdout);
|
|
535
852
|
if (outcome.aborted) {
|
|
536
853
|
return { ok: false, aborted: true, output: answer.text, durationMs: outcome.durationMs, command };
|
|
537
854
|
}
|
|
@@ -567,8 +884,10 @@ var init_runtimes = __esm({
|
|
|
567
884
|
"src/agents/runtimes.ts"() {
|
|
568
885
|
init_runtime();
|
|
569
886
|
init_runtime2();
|
|
887
|
+
init_runtime3();
|
|
570
888
|
RUNTIMES2 = {
|
|
571
889
|
codex: codexRuntime,
|
|
890
|
+
claude: claudeRuntime,
|
|
572
891
|
"cursor-agent": cursorAgentRuntime
|
|
573
892
|
};
|
|
574
893
|
}
|
|
@@ -580,7 +899,7 @@ import { createServer } from "node:http";
|
|
|
580
899
|
// src/server/signals.ts
|
|
581
900
|
import { mkdtempSync } from "node:fs";
|
|
582
901
|
import { tmpdir } from "node:os";
|
|
583
|
-
import { join as
|
|
902
|
+
import { join as join8 } from "node:path";
|
|
584
903
|
|
|
585
904
|
// node_modules/.pnpm/drizzle-orm@0.45.2_@libsql+client@0.18.0_@types+better-sqlite3@9.6.0_better-sqlite3@13.0.3/node_modules/drizzle-orm/entity.js
|
|
586
905
|
var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
|
|
@@ -823,8 +1142,8 @@ var ForeignKey = class {
|
|
|
823
1142
|
};
|
|
824
1143
|
|
|
825
1144
|
// node_modules/.pnpm/drizzle-orm@0.45.2_@libsql+client@0.18.0_@types+better-sqlite3@9.6.0_better-sqlite3@13.0.3/node_modules/drizzle-orm/tracing-utils.js
|
|
826
|
-
function iife(fn, ...
|
|
827
|
-
return fn(...
|
|
1145
|
+
function iife(fn, ...args4) {
|
|
1146
|
+
return fn(...args4);
|
|
828
1147
|
}
|
|
829
1148
|
|
|
830
1149
|
// node_modules/.pnpm/drizzle-orm@0.45.2_@libsql+client@0.18.0_@types+better-sqlite3@9.6.0_better-sqlite3@13.0.3/node_modules/drizzle-orm/pg-core/unique-constraint.js
|
|
@@ -1612,7 +1931,7 @@ function sql(strings, ...params) {
|
|
|
1612
1931
|
return new SQL([new StringChunk(str)]);
|
|
1613
1932
|
}
|
|
1614
1933
|
sql2.raw = raw;
|
|
1615
|
-
function
|
|
1934
|
+
function join9(chunks, separator) {
|
|
1616
1935
|
const result = [];
|
|
1617
1936
|
for (const [i, chunk] of chunks.entries()) {
|
|
1618
1937
|
if (i > 0 && separator !== void 0) {
|
|
@@ -1622,7 +1941,7 @@ function sql(strings, ...params) {
|
|
|
1622
1941
|
}
|
|
1623
1942
|
return new SQL(result);
|
|
1624
1943
|
}
|
|
1625
|
-
sql2.join =
|
|
1944
|
+
sql2.join = join9;
|
|
1626
1945
|
function identifier(value) {
|
|
1627
1946
|
return new Name(value);
|
|
1628
1947
|
}
|
|
@@ -2490,7 +2809,7 @@ var repositories = {
|
|
|
2490
2809
|
key: "repositories",
|
|
2491
2810
|
kind: "string_list",
|
|
2492
2811
|
label: "Repositories",
|
|
2493
|
-
help: "owner/name, one per line.
|
|
2812
|
+
help: "owner/name, one per line. acme/* works. Empty means all.",
|
|
2494
2813
|
placeholder: "acme/web"
|
|
2495
2814
|
};
|
|
2496
2815
|
var ignoreDrafts = {
|
|
@@ -2503,7 +2822,7 @@ var ignoreBots = {
|
|
|
2503
2822
|
key: "ignoreBots",
|
|
2504
2823
|
kind: "boolean",
|
|
2505
2824
|
label: "Skip pull requests opened by bots",
|
|
2506
|
-
help: "
|
|
2825
|
+
help: "Such as Dependabot.",
|
|
2507
2826
|
default: true
|
|
2508
2827
|
};
|
|
2509
2828
|
var sizeLimit = {
|
|
@@ -2522,7 +2841,7 @@ var issueTarget = {
|
|
|
2522
2841
|
key: "issue",
|
|
2523
2842
|
kind: "text",
|
|
2524
2843
|
label: "Issue or pull request",
|
|
2525
|
-
help: "
|
|
2844
|
+
help: "Empty replies where it was asked. Or a GitHub issue URL.",
|
|
2526
2845
|
placeholder: "https://github.com/acme/web/issues/12"
|
|
2527
2846
|
};
|
|
2528
2847
|
var REVIEW_PROMPT = [
|
|
@@ -2540,94 +2859,77 @@ var REVIEW_PROMPT = [
|
|
|
2540
2859
|
"Where you cannot see enough of the code to judge something, say so instead of",
|
|
2541
2860
|
"guessing."
|
|
2542
2861
|
].join("\n");
|
|
2543
|
-
var
|
|
2544
|
-
"
|
|
2545
|
-
"",
|
|
2546
|
-
"Say what should change, in which files where you can tell, and what to watch",
|
|
2547
|
-
"out for. Stay inside what the issue actually supports: if it is too vague to",
|
|
2548
|
-
"plan, say what is missing rather than inventing the requirements.",
|
|
2549
|
-
"",
|
|
2550
|
-
"Do not write the implementation."
|
|
2551
|
-
].join("\n");
|
|
2552
|
-
var IMPLEMENT_PROMPT = [
|
|
2553
|
-
"Implement this issue in the checkout you are in.",
|
|
2862
|
+
var REPLY_PROMPT = [
|
|
2863
|
+
"Answer this issue in a comment, the way a teammate who knows this codebase would.",
|
|
2554
2864
|
"",
|
|
2555
|
-
"
|
|
2556
|
-
"
|
|
2557
|
-
"
|
|
2865
|
+
"Work out what the issue is asking for. If it is a question, answer it from the",
|
|
2866
|
+
"code rather than from memory. If it reports a bug, find the likely cause and",
|
|
2867
|
+
"point at the files and lines involved. If it asks for a change, say how you",
|
|
2868
|
+
"would make it, where, and what to watch out for.",
|
|
2558
2869
|
"",
|
|
2559
|
-
"
|
|
2560
|
-
"
|
|
2561
|
-
"or doing it properly needs a decision that is not yours to make, reply with",
|
|
2562
|
-
"exactly NOTHING_TO_DO rather than guessing: an unwanted pull request costs",
|
|
2563
|
-
"more attention than a missing one.",
|
|
2564
|
-
"",
|
|
2565
|
-
"Run the project's tests if you can work out how, and say in your description",
|
|
2566
|
-
"whether you did and what happened."
|
|
2870
|
+
"Read the code; do not change it. If the issue is too vague to answer, say what",
|
|
2871
|
+
"is missing rather than inventing it."
|
|
2567
2872
|
].join("\n");
|
|
2568
2873
|
var githubManifest = defineManifest({
|
|
2569
2874
|
id: "github",
|
|
2570
2875
|
name: "GitHub",
|
|
2571
|
-
tagline: "Pick up
|
|
2876
|
+
tagline: "Pick up reviews and issues sent to your team's shared GitHub account.",
|
|
2572
2877
|
docsUrl: "https://docs.github.com/rest",
|
|
2573
2878
|
icon: "Github",
|
|
2574
2879
|
accent: "bg-neutral-900 text-white",
|
|
2575
2880
|
auth: {
|
|
2576
2881
|
kind: "oauth_redirect",
|
|
2577
2882
|
scopes: GITHUB_SCOPES,
|
|
2578
|
-
needsAppRegistration: true
|
|
2883
|
+
needsAppRegistration: true,
|
|
2884
|
+
// The Connection is the team's agent on GitHub: whatever it writes appears
|
|
2885
|
+
// under its name, and the work it picks up is whatever the team sends to
|
|
2886
|
+
// it. An account made for this is cleanest, but a teammate's own works too
|
|
2887
|
+
// if they are happy for it to be shared.
|
|
2888
|
+
note: "Connect the account the team will send work to. An account made for this is cleanest, but your own works too if you are happy to share it. Reviews and comments appear under its name, and it sees only the repositories it can access. Sign in to GitHub as that account first; GitHub authorizes whichever account the browser is signed in as."
|
|
2579
2889
|
},
|
|
2580
|
-
|
|
2890
|
+
triggers: [
|
|
2581
2891
|
{
|
|
2582
2892
|
id: "github.review_requested",
|
|
2583
|
-
|
|
2584
|
-
summary: "Reads the change and posts a review whenever someone asks for yours.",
|
|
2893
|
+
when: "A GitHub review is requested",
|
|
2585
2894
|
// Team requests matter more than they sound: in most repositories with a
|
|
2586
2895
|
// CODEOWNERS file, review arrives addressed to a team rather than a person.
|
|
2587
|
-
trigger: "
|
|
2896
|
+
trigger: "someone requests a review from the connected account, directly or through its team",
|
|
2588
2897
|
// review-requested covers teams; user-review-requested would not.
|
|
2589
2898
|
watches: "is:open is:pr review-requested:@me",
|
|
2590
|
-
writes: "a review on the pull request, as a comment rather than an approval",
|
|
2591
2899
|
settings: [repositories, ignoreDrafts, ignoreBots, sizeLimit],
|
|
2592
|
-
prompt: REVIEW_PROMPT,
|
|
2593
|
-
guidancePlaceholder: "Anything specific to your team. For example: we require a test for every new endpoint.",
|
|
2594
|
-
answer: "review",
|
|
2595
2900
|
runsIn: "checkout",
|
|
2901
|
+
answer: "review",
|
|
2596
2902
|
actionId: "github.submit_review"
|
|
2597
2903
|
},
|
|
2598
2904
|
{
|
|
2599
2905
|
id: "github.issue_assigned",
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
// is:issue matters: without it this would pick up your own pull requests.
|
|
2906
|
+
when: "A GitHub issue is assigned",
|
|
2907
|
+
trigger: "an issue is assigned to the connected account",
|
|
2908
|
+
// is:issue matters: without it this would pick up pull requests too.
|
|
2604
2909
|
watches: "is:open is:issue assignee:@me",
|
|
2605
|
-
writes: "a comment on the issue",
|
|
2606
2910
|
settings: [repositories],
|
|
2607
|
-
prompt: PLAN_PROMPT,
|
|
2608
|
-
guidancePlaceholder: "Anything specific to this codebase worth knowing before planning.",
|
|
2609
|
-
answer: "text",
|
|
2610
2911
|
runsIn: "checkout",
|
|
2912
|
+
answer: "text",
|
|
2611
2913
|
actionId: "github.post_issue_comment"
|
|
2914
|
+
}
|
|
2915
|
+
],
|
|
2916
|
+
workflows: [
|
|
2917
|
+
{
|
|
2918
|
+
id: "github.review_requested",
|
|
2919
|
+
name: "Review pull requests",
|
|
2920
|
+
summary: "Posts a review when someone requests one from the connected account.",
|
|
2921
|
+
triggerId: "github.review_requested",
|
|
2922
|
+
prompt: REVIEW_PROMPT
|
|
2612
2923
|
},
|
|
2613
2924
|
{
|
|
2614
|
-
id
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
// rather than replacing it: asking for a plan and asking for the change
|
|
2623
|
-
// are different jobs, and which one an issue deserves is a judgement
|
|
2624
|
-
// about the issue.
|
|
2625
|
-
runsIn: "checkout",
|
|
2626
|
-
settings: [repositories],
|
|
2627
|
-
prompt: IMPLEMENT_PROMPT,
|
|
2628
|
-
guidancePlaceholder: "How work is done here. For example: every new endpoint needs a test, and we do not add dependencies without asking.",
|
|
2629
|
-
answer: "code",
|
|
2630
|
-
actionId: "github.open_pull_request"
|
|
2925
|
+
// The id is the one "Plan issues assigned to me" had. Planning was one
|
|
2926
|
+
// kind of answer to an issue; replying covers it and the rest, and loops
|
|
2927
|
+
// made for planning keep the prompt they copied.
|
|
2928
|
+
id: "github.issue_assigned",
|
|
2929
|
+
name: "Reply to issues",
|
|
2930
|
+
summary: "Answers an issue in a comment when it is assigned to the connected account.",
|
|
2931
|
+
triggerId: "github.issue_assigned",
|
|
2932
|
+
prompt: REPLY_PROMPT
|
|
2631
2933
|
}
|
|
2632
2934
|
],
|
|
2633
2935
|
actions: [
|
|
@@ -2650,80 +2952,59 @@ var githubManifest = defineManifest({
|
|
|
2650
2952
|
// A comment is one body of markdown with nowhere to attach anything, so
|
|
2651
2953
|
// a review arrives here with its findings written into the prose.
|
|
2652
2954
|
accepts: ["text"]
|
|
2653
|
-
},
|
|
2654
|
-
{
|
|
2655
|
-
id: "github.open_pull_request",
|
|
2656
|
-
name: "Open a draft pull request",
|
|
2657
|
-
summary: "Open a draft pull request for the branch the agent already pushed.",
|
|
2658
|
-
// The agent cloned and pushed with the machine's git. This action only
|
|
2659
|
-
// names the pull request through the Connection.
|
|
2660
|
-
target: [],
|
|
2661
|
-
accepts: ["code"]
|
|
2662
2955
|
}
|
|
2663
2956
|
],
|
|
2664
2957
|
// Nothing to configure per account: what the account can see is what GitHub
|
|
2665
2958
|
// decides, and every narrowing choice belongs to a loop.
|
|
2666
2959
|
settings: [],
|
|
2667
|
-
//
|
|
2668
|
-
// as, so a second
|
|
2669
|
-
// first. Connections are still rows, so this
|
|
2960
|
+
// One shared account per team. The redirect also authorizes whichever account
|
|
2961
|
+
// the browser is already signed in as, so a second one is out of reach
|
|
2962
|
+
// without signing out of GitHub first. Connections are still rows, so this
|
|
2963
|
+
// is a product decision only.
|
|
2670
2964
|
allowsMultipleAccounts: false,
|
|
2671
2965
|
byHand: { kind: "link", placeholder: "https://github.com/acme/web/pull/123" }
|
|
2672
2966
|
});
|
|
2673
2967
|
|
|
2674
2968
|
// src/connectors/gmail/manifest.ts
|
|
2675
|
-
var GMAIL_SCOPES = [
|
|
2969
|
+
var GMAIL_SCOPES = [
|
|
2970
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
2971
|
+
"https://www.googleapis.com/auth/gmail.send"
|
|
2972
|
+
];
|
|
2676
2973
|
var extraQuery = {
|
|
2677
2974
|
key: "extraQuery",
|
|
2678
2975
|
kind: "text",
|
|
2679
|
-
label: "
|
|
2680
|
-
help: "Gmail search terms,
|
|
2681
|
-
placeholder: "from:
|
|
2976
|
+
label: "Only mail matching",
|
|
2977
|
+
help: "Gmail search terms, such as from:billing@vendor.com or subject:invoice.",
|
|
2978
|
+
placeholder: "from:alerts@example.com"
|
|
2979
|
+
};
|
|
2980
|
+
var needsMeOnly = {
|
|
2981
|
+
key: "needsMeOnly",
|
|
2982
|
+
kind: "boolean",
|
|
2983
|
+
label: "Skip mail that needs nothing",
|
|
2984
|
+
help: "A quick look first decides which mail asks for something. The rest is kept, not run.",
|
|
2985
|
+
default: true
|
|
2682
2986
|
};
|
|
2683
2987
|
var perPoll = {
|
|
2684
2988
|
key: "maxPerPoll",
|
|
2685
2989
|
kind: "select",
|
|
2686
|
-
label: "At most, per
|
|
2687
|
-
help: "Each mail costs a full agent run. A quiet mailbox never reaches this; a busy one should be narrowed above rather than raised here.",
|
|
2990
|
+
label: "At most, per check",
|
|
2688
2991
|
options: [
|
|
2689
|
-
{ value: "3", label: "3
|
|
2690
|
-
{ value: "10", label: "10
|
|
2691
|
-
{ value: "25", label: "25
|
|
2992
|
+
{ value: "3", label: "3 emails" },
|
|
2993
|
+
{ value: "10", label: "10 emails" },
|
|
2994
|
+
{ value: "25", label: "25 emails" }
|
|
2692
2995
|
],
|
|
2693
2996
|
default: "10"
|
|
2694
2997
|
};
|
|
2695
|
-
var needsMeOnly = {
|
|
2696
|
-
key: "needsMeOnly",
|
|
2697
|
-
kind: "boolean",
|
|
2698
|
-
label: "Only tell me when it needs me",
|
|
2699
|
-
help: "One cheap look at everything that arrived decides which mails are actually asking something of you, from the sender, subject and first line. The rest wait in the backlog with a reason, so you can still run one if this got it wrong.",
|
|
2700
|
-
default: true
|
|
2701
|
-
};
|
|
2702
2998
|
var skipOwn = {
|
|
2703
2999
|
key: "skipOwn",
|
|
2704
3000
|
kind: "boolean",
|
|
2705
|
-
label: "Skip mail
|
|
2706
|
-
help: "Your own messages show up in threads you are part of, and being told about what you just sent is not useful.",
|
|
3001
|
+
label: "Skip mail sent from this account",
|
|
2707
3002
|
default: true
|
|
2708
3003
|
};
|
|
2709
|
-
var TELL_ME_PROMPT = [
|
|
2710
|
-
"Read the email in EMAIL.md and tell me what it says.",
|
|
2711
|
-
"",
|
|
2712
|
-
"You are writing a phone notification, not a summary document. Two or three",
|
|
2713
|
-
'sentences. No headings, no bullet lists, no preamble like "this email is".',
|
|
2714
|
-
"Lead with who it is from and what they want.",
|
|
2715
|
-
"",
|
|
2716
|
-
"Say plainly if it needs something from me and by when. If there is a",
|
|
2717
|
-
"deadline, an amount of money, a date, or a link I have to act on, include it",
|
|
2718
|
-
"exactly as written rather than describing it.",
|
|
2719
|
-
"",
|
|
2720
|
-
"If it is an automated notification, a newsletter, or otherwise needs nothing",
|
|
2721
|
-
"from me, say so in one line and stop."
|
|
2722
|
-
].join("\n");
|
|
2723
3004
|
var gmailManifest = defineManifest({
|
|
2724
3005
|
id: "gmail",
|
|
2725
3006
|
name: "Gmail",
|
|
2726
|
-
tagline: "
|
|
3007
|
+
tagline: "Start a loop when an email arrives, or email an answer to the connected account.",
|
|
2727
3008
|
docsUrl: "https://developers.google.com/gmail/api/guides",
|
|
2728
3009
|
icon: "Gmail",
|
|
2729
3010
|
// Gmail's own red, now that the mark is Gmail's own too. A near miss on a
|
|
@@ -2734,45 +3015,40 @@ var gmailManifest = defineManifest({
|
|
|
2734
3015
|
scopes: GMAIL_SCOPES,
|
|
2735
3016
|
needsAppRegistration: true
|
|
2736
3017
|
},
|
|
2737
|
-
|
|
3018
|
+
triggers: [
|
|
2738
3019
|
{
|
|
2739
3020
|
id: "gmail.new_mail",
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
trigger: "mail arrives in your inbox",
|
|
3021
|
+
when: "An email arrives",
|
|
3022
|
+
trigger: "an email arrives in the connected inbox",
|
|
2743
3023
|
// A day's window rather than "unread": reading a mail on your phone
|
|
2744
3024
|
// should not decide whether the loop ever saw it. What stops a mail
|
|
2745
|
-
// being handled twice is its id, not its state
|
|
2746
|
-
// costs nothing and a poller that was off for an hour misses nothing.
|
|
3025
|
+
// being handled twice is its id, not its state.
|
|
2747
3026
|
watches: "in:inbox newer_than:1d",
|
|
2748
|
-
|
|
2749
|
-
// Nothing to check out: the mail is the whole of the work, and it is
|
|
2750
|
-
// handed over as a file.
|
|
3027
|
+
// The mail is the whole of the work, handed over as a file.
|
|
2751
3028
|
runsIn: "temp",
|
|
2752
3029
|
settings: [extraQuery, needsMeOnly, perPoll, skipOwn],
|
|
2753
|
-
prompt: TELL_ME_PROMPT,
|
|
2754
|
-
guidancePlaceholder: "What you care about in your mail. For example: I only need to know about anything involving money or a deadline.",
|
|
2755
3030
|
answer: "text",
|
|
2756
|
-
//
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
//
|
|
2760
|
-
|
|
2761
|
-
actionId: "wechat.reply",
|
|
2762
|
-
actionTarget: { to: "me" },
|
|
2763
|
-
// Half an hour, because mail turns up one piece at a time and the whole
|
|
2764
|
-
// of deciding what is worth a run is having several to compare. Looking
|
|
2765
|
-
// every two minutes meant almost always finding exactly one, which is
|
|
2766
|
-
// not a batch, so every newsletter got an agent to itself.
|
|
2767
|
-
//
|
|
2768
|
-
// The cost of waiting is being told half an hour late about something
|
|
2769
|
-
// that sat unread for hours before it was sent.
|
|
3031
|
+
// Kept in Inbox until a loop says where it should go.
|
|
3032
|
+
actionConnectorId: "schedule",
|
|
3033
|
+
actionId: "schedule.record",
|
|
3034
|
+
// Mail turns up one piece at a time, and deciding what deserves a run
|
|
3035
|
+
// needs several to compare.
|
|
2770
3036
|
pollEveryMs: 30 * 6e4
|
|
2771
3037
|
}
|
|
2772
3038
|
],
|
|
2773
|
-
//
|
|
2774
|
-
//
|
|
2775
|
-
|
|
3039
|
+
// No ready-made job: what to do with mail is the team's to say, in a
|
|
3040
|
+
// custom loop.
|
|
3041
|
+
workflows: [],
|
|
3042
|
+
actions: [
|
|
3043
|
+
{
|
|
3044
|
+
id: "gmail.send",
|
|
3045
|
+
name: "Email this account",
|
|
3046
|
+
summary: "Send the answer to the connected account's own inbox, in the same thread when it started from an email there.",
|
|
3047
|
+
target: [],
|
|
3048
|
+
// A review sent here reads as prose, with file and line written in.
|
|
3049
|
+
accepts: ["text"]
|
|
3050
|
+
}
|
|
3051
|
+
],
|
|
2776
3052
|
settings: [],
|
|
2777
3053
|
// The redirect authorizes whichever account the browser is signed in as, so
|
|
2778
3054
|
// reaching a second one means signing out of Google first.
|
|
@@ -2868,24 +3144,17 @@ var at = {
|
|
|
2868
3144
|
key: "at",
|
|
2869
3145
|
kind: "text",
|
|
2870
3146
|
label: "At",
|
|
2871
|
-
help: "
|
|
3147
|
+
help: "24-hour time, in this host's time zone.",
|
|
2872
3148
|
placeholder: "09:00"
|
|
2873
3149
|
};
|
|
2874
3150
|
var weekday = {
|
|
2875
3151
|
key: "weekday",
|
|
2876
3152
|
kind: "select",
|
|
2877
3153
|
label: "On",
|
|
2878
|
-
help: "
|
|
3154
|
+
help: "For weekly loops.",
|
|
2879
3155
|
options: WEEKDAY_NAMES.map((name, index2) => ({ value: String(index2), label: name })),
|
|
2880
3156
|
default: "1"
|
|
2881
3157
|
};
|
|
2882
|
-
var folder = {
|
|
2883
|
-
key: "folder",
|
|
2884
|
-
kind: "text",
|
|
2885
|
-
label: "Folder to work in",
|
|
2886
|
-
help: "An absolute path. Nothing arrives to be looked at on a schedule, so this is the whole of what the agent is given.",
|
|
2887
|
-
placeholder: "/Users/you/code/web"
|
|
2888
|
-
};
|
|
2889
3158
|
var RECURRING_PROMPT = [
|
|
2890
3159
|
"Nothing has arrived: this is running because the time came round.",
|
|
2891
3160
|
"",
|
|
@@ -2907,28 +3176,33 @@ var scheduleManifest = defineManifest({
|
|
|
2907
3176
|
allowsMultipleAccounts: false,
|
|
2908
3177
|
byHand: { kind: "now" },
|
|
2909
3178
|
settings: [],
|
|
2910
|
-
|
|
3179
|
+
triggers: [
|
|
2911
3180
|
{
|
|
2912
3181
|
id: "schedule.recurring",
|
|
2913
|
-
|
|
2914
|
-
summary: "Runs an agent in a folder you name, and keeps what it says.",
|
|
3182
|
+
when: "On a schedule",
|
|
2915
3183
|
trigger: "the time you set comes round",
|
|
2916
3184
|
// No query to show. A clock is not something one can be written for,
|
|
2917
3185
|
// and an invented one shown as if it were real is worse than nothing.
|
|
2918
|
-
writes: "a note in the log",
|
|
2919
3186
|
runsIn: "folder",
|
|
2920
|
-
settings: [every, at, weekday
|
|
2921
|
-
prompt: RECURRING_PROMPT,
|
|
2922
|
-
guidancePlaceholder: "Anything that holds every time this runs. For example: this repository releases on Thursdays, so say if anything is unmerged by Wednesday evening.",
|
|
3187
|
+
settings: [every, at, weekday],
|
|
2923
3188
|
answer: "text",
|
|
2924
3189
|
actionId: "schedule.record"
|
|
2925
3190
|
}
|
|
2926
3191
|
],
|
|
3192
|
+
workflows: [
|
|
3193
|
+
{
|
|
3194
|
+
id: "schedule.recurring",
|
|
3195
|
+
name: "Do something on a schedule",
|
|
3196
|
+
summary: "Runs the agent at the times you set, in a folder you name. The answer stays in Inbox, or goes to Slack, Lark, or GitHub.",
|
|
3197
|
+
triggerId: "schedule.recurring",
|
|
3198
|
+
prompt: RECURRING_PROMPT
|
|
3199
|
+
}
|
|
3200
|
+
],
|
|
2927
3201
|
actions: [
|
|
2928
3202
|
{
|
|
2929
3203
|
id: "schedule.record",
|
|
2930
|
-
name: "Keep
|
|
2931
|
-
summary: "
|
|
3204
|
+
name: "Keep in Inbox",
|
|
3205
|
+
summary: "Keeps the answer in Inbox and sends it nowhere.",
|
|
2932
3206
|
target: [],
|
|
2933
3207
|
accepts: ["text", "review"]
|
|
2934
3208
|
}
|
|
@@ -2936,18 +3210,11 @@ var scheduleManifest = defineManifest({
|
|
|
2936
3210
|
});
|
|
2937
3211
|
|
|
2938
3212
|
// src/connectors/slack/manifest.ts
|
|
2939
|
-
var folder2 = {
|
|
2940
|
-
key: "folder",
|
|
2941
|
-
kind: "text",
|
|
2942
|
-
label: "Folder to work in",
|
|
2943
|
-
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
2944
|
-
placeholder: "/Users/you/code/your-project"
|
|
2945
|
-
};
|
|
2946
3213
|
var channel = {
|
|
2947
3214
|
key: "channel",
|
|
2948
3215
|
kind: "text",
|
|
2949
3216
|
label: "Channel",
|
|
2950
|
-
help: "
|
|
3217
|
+
help: "Empty replies where it was asked. Or a channel ID.",
|
|
2951
3218
|
placeholder: "C0123456789"
|
|
2952
3219
|
};
|
|
2953
3220
|
var ASK_PROMPT = [
|
|
@@ -2982,22 +3249,27 @@ var slackManifest = defineManifest({
|
|
|
2982
3249
|
}
|
|
2983
3250
|
]
|
|
2984
3251
|
},
|
|
2985
|
-
|
|
3252
|
+
triggers: [
|
|
2986
3253
|
{
|
|
2987
3254
|
id: "slack.ask",
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
trigger: "the bot is @mentioned, or someone DMs it",
|
|
2991
|
-
writes: "a reply in the Slack thread",
|
|
3255
|
+
when: "The Slack bot is mentioned",
|
|
3256
|
+
trigger: "someone @mentions the Slack bot in a channel it is in, or DMs it",
|
|
2992
3257
|
runsIn: "folder",
|
|
2993
|
-
settings: [
|
|
2994
|
-
prompt: ASK_PROMPT,
|
|
2995
|
-
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
3258
|
+
settings: [],
|
|
2996
3259
|
answer: "text",
|
|
2997
3260
|
actionId: "slack.reply",
|
|
2998
3261
|
pollEveryMs: 6e4
|
|
2999
3262
|
}
|
|
3000
3263
|
],
|
|
3264
|
+
workflows: [
|
|
3265
|
+
{
|
|
3266
|
+
id: "slack.ask",
|
|
3267
|
+
name: "Do what I ask the Slack bot",
|
|
3268
|
+
summary: "Runs the agent when someone @mentions the Slack bot or DMs it, and answers in the thread.",
|
|
3269
|
+
triggerId: "slack.ask",
|
|
3270
|
+
prompt: ASK_PROMPT
|
|
3271
|
+
}
|
|
3272
|
+
],
|
|
3001
3273
|
actions: [
|
|
3002
3274
|
{
|
|
3003
3275
|
id: "slack.reply",
|
|
@@ -3013,18 +3285,11 @@ var slackManifest = defineManifest({
|
|
|
3013
3285
|
});
|
|
3014
3286
|
|
|
3015
3287
|
// src/connectors/feishu/manifest.ts
|
|
3016
|
-
var folder3 = {
|
|
3017
|
-
key: "folder",
|
|
3018
|
-
kind: "text",
|
|
3019
|
-
label: "Folder to work in",
|
|
3020
|
-
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
3021
|
-
placeholder: "/Users/you/code/your-project"
|
|
3022
|
-
};
|
|
3023
3288
|
var chat = {
|
|
3024
3289
|
key: "chat",
|
|
3025
3290
|
kind: "text",
|
|
3026
3291
|
label: "Chat ID",
|
|
3027
|
-
help: "
|
|
3292
|
+
help: "Empty replies where it was asked. Or a chat ID (oc_\u2026).",
|
|
3028
3293
|
placeholder: "oc_..."
|
|
3029
3294
|
};
|
|
3030
3295
|
var ASK_PROMPT2 = [
|
|
@@ -3072,26 +3337,31 @@ var feishuManifest = defineManifest({
|
|
|
3072
3337
|
}
|
|
3073
3338
|
]
|
|
3074
3339
|
},
|
|
3075
|
-
|
|
3340
|
+
triggers: [
|
|
3076
3341
|
{
|
|
3077
3342
|
id: "feishu.ask",
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
trigger: "the bot is @mentioned in a group it is in",
|
|
3081
|
-
writes: "a reply in the Feishu thread",
|
|
3343
|
+
when: "The Feishu / Lark bot is mentioned",
|
|
3344
|
+
trigger: "someone @mentions the Feishu / Lark bot in a group it is in",
|
|
3082
3345
|
runsIn: "folder",
|
|
3083
|
-
settings: [
|
|
3084
|
-
prompt: ASK_PROMPT2,
|
|
3085
|
-
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
3346
|
+
settings: [],
|
|
3086
3347
|
answer: "text",
|
|
3087
3348
|
actionId: "feishu.reply",
|
|
3088
3349
|
pollEveryMs: 6e4
|
|
3089
3350
|
}
|
|
3090
3351
|
],
|
|
3352
|
+
workflows: [
|
|
3353
|
+
{
|
|
3354
|
+
id: "feishu.ask",
|
|
3355
|
+
name: "Do what I ask the Feishu / Lark bot",
|
|
3356
|
+
summary: "Runs the agent when someone @mentions the Feishu / Lark bot in a group, and answers in the thread.",
|
|
3357
|
+
triggerId: "feishu.ask",
|
|
3358
|
+
prompt: ASK_PROMPT2
|
|
3359
|
+
}
|
|
3360
|
+
],
|
|
3091
3361
|
actions: [
|
|
3092
3362
|
{
|
|
3093
3363
|
id: "feishu.reply",
|
|
3094
|
-
name: "Reply in Feishu",
|
|
3364
|
+
name: "Reply in Feishu / Lark",
|
|
3095
3365
|
summary: "Post a message back to the thread that asked, or to a chat you name.",
|
|
3096
3366
|
target: [chat],
|
|
3097
3367
|
accepts: ["text"]
|
|
@@ -3103,13 +3373,6 @@ var feishuManifest = defineManifest({
|
|
|
3103
3373
|
});
|
|
3104
3374
|
|
|
3105
3375
|
// src/connectors/wechat/manifest.ts
|
|
3106
|
-
var folder4 = {
|
|
3107
|
-
key: "folder",
|
|
3108
|
-
kind: "text",
|
|
3109
|
-
label: "Folder to work in",
|
|
3110
|
-
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
3111
|
-
placeholder: "/Users/you/code/your-project"
|
|
3112
|
-
};
|
|
3113
3376
|
var askers = {
|
|
3114
3377
|
key: "askers",
|
|
3115
3378
|
kind: "select",
|
|
@@ -3124,7 +3387,7 @@ var recipient = {
|
|
|
3124
3387
|
key: "to",
|
|
3125
3388
|
kind: "select",
|
|
3126
3389
|
label: "Who to send it to",
|
|
3127
|
-
help: "
|
|
3390
|
+
help: '"Me" only arrives if you messaged the bot in the last half day.',
|
|
3128
3391
|
options: [
|
|
3129
3392
|
{ value: "source", label: "Whoever asked" },
|
|
3130
3393
|
{ value: "me", label: "Me" }
|
|
@@ -3155,23 +3418,28 @@ var wechatManifest = {
|
|
|
3155
3418
|
kind: "qr_scan",
|
|
3156
3419
|
note: "Scanning binds a bot to your WeChat account. Loopable can then read what people send that bot and answer as it, and nothing else on your account."
|
|
3157
3420
|
},
|
|
3158
|
-
|
|
3421
|
+
triggers: [
|
|
3159
3422
|
{
|
|
3160
3423
|
id: "wechat.ask",
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
trigger: "you send the bot a message",
|
|
3424
|
+
when: "The WeChat bot gets a message",
|
|
3425
|
+
trigger: "someone sends the WeChat bot a message",
|
|
3164
3426
|
// No query to show: messages arrive on a stream rather than being found
|
|
3165
3427
|
// by asking, so there is nothing here that could be checked.
|
|
3166
|
-
writes: "a reply in the chat",
|
|
3167
3428
|
runsIn: "folder",
|
|
3168
|
-
settings: [
|
|
3169
|
-
prompt: ASK_PROMPT3,
|
|
3170
|
-
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
3429
|
+
settings: [askers],
|
|
3171
3430
|
answer: "text",
|
|
3172
3431
|
actionId: "wechat.reply"
|
|
3173
3432
|
}
|
|
3174
3433
|
],
|
|
3434
|
+
workflows: [
|
|
3435
|
+
{
|
|
3436
|
+
id: "wechat.ask",
|
|
3437
|
+
name: "Do what I ask the WeChat bot",
|
|
3438
|
+
summary: "Runs the agent when someone messages the WeChat bot, and answers in the chat.",
|
|
3439
|
+
triggerId: "wechat.ask",
|
|
3440
|
+
prompt: ASK_PROMPT3
|
|
3441
|
+
}
|
|
3442
|
+
],
|
|
3175
3443
|
actions: [
|
|
3176
3444
|
{
|
|
3177
3445
|
id: "wechat.reply",
|
|
@@ -3206,8 +3474,36 @@ function connectorManifest(id) {
|
|
|
3206
3474
|
function needsAccount(id) {
|
|
3207
3475
|
return connectorManifest(id)?.auth.kind !== "none";
|
|
3208
3476
|
}
|
|
3477
|
+
function customOn(trigger) {
|
|
3478
|
+
return {
|
|
3479
|
+
...trigger,
|
|
3480
|
+
triggerId: trigger.id,
|
|
3481
|
+
name: trigger.when,
|
|
3482
|
+
summary: `When ${trigger.trigger}.`,
|
|
3483
|
+
prompt: "",
|
|
3484
|
+
custom: true
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3209
3487
|
function connectorWorkflow(connectorId, workflowId) {
|
|
3210
|
-
|
|
3488
|
+
const manifest = connectorManifest(connectorId);
|
|
3489
|
+
if (!manifest) return void 0;
|
|
3490
|
+
const defined = manifest.workflows.find((entry) => entry.id === workflowId);
|
|
3491
|
+
if (defined) {
|
|
3492
|
+
const trigger2 = manifest.triggers.find((entry) => entry.id === defined.triggerId);
|
|
3493
|
+
if (!trigger2) return void 0;
|
|
3494
|
+
return {
|
|
3495
|
+
...trigger2,
|
|
3496
|
+
...defined,
|
|
3497
|
+
actionId: defined.actionId ?? trigger2.actionId,
|
|
3498
|
+
actionConnectorId: defined.actionConnectorId ?? trigger2.actionConnectorId,
|
|
3499
|
+
actionTarget: defined.actionTarget ?? trigger2.actionTarget,
|
|
3500
|
+
pollEveryMs: defined.pollEveryMs ?? trigger2.pollEveryMs,
|
|
3501
|
+
triggerId: trigger2.id,
|
|
3502
|
+
custom: false
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
const trigger = manifest.triggers.find((entry) => entry.id === workflowId);
|
|
3506
|
+
return trigger ? customOn(trigger) : void 0;
|
|
3211
3507
|
}
|
|
3212
3508
|
function connectorAction(connectorId, actionId) {
|
|
3213
3509
|
return connectorManifest(connectorId)?.actions.find((entry) => entry.id === actionId);
|
|
@@ -3614,10 +3910,10 @@ function candidates(items, repositories2) {
|
|
|
3614
3910
|
return repo && repoMatches(repo, repositories2) ? [{ repo, item }] : [];
|
|
3615
3911
|
});
|
|
3616
3912
|
}
|
|
3617
|
-
function query(
|
|
3618
|
-
const
|
|
3619
|
-
if (!
|
|
3620
|
-
return
|
|
3913
|
+
function query(triggerId) {
|
|
3914
|
+
const trigger = githubManifest.triggers.find((entry) => entry.id === triggerId);
|
|
3915
|
+
if (!trigger?.watches) throw new Error(`GitHub cannot watch for ${triggerId}.`);
|
|
3916
|
+
return trigger.watches;
|
|
3621
3917
|
}
|
|
3622
3918
|
async function pollGithub(input) {
|
|
3623
3919
|
const repositories2 = stringList(input.settings.repositories);
|
|
@@ -3893,11 +4189,17 @@ var githubRuntime = defineRuntime({
|
|
|
3893
4189
|
|
|
3894
4190
|
// src/connectors/gmail/api.ts
|
|
3895
4191
|
var BASE = "https://gmail.googleapis.com/gmail/v1/users/me";
|
|
3896
|
-
async function request2(accessToken, path) {
|
|
4192
|
+
async function request2(accessToken, path, init = {}) {
|
|
3897
4193
|
let res;
|
|
3898
4194
|
try {
|
|
3899
4195
|
res = await fetch(`${BASE}${path}`, {
|
|
3900
|
-
|
|
4196
|
+
method: init.method ?? "GET",
|
|
4197
|
+
headers: {
|
|
4198
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4199
|
+
Accept: "application/json",
|
|
4200
|
+
...init.body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
4201
|
+
},
|
|
4202
|
+
...init.body === void 0 ? {} : { body: JSON.stringify(init.body) }
|
|
3901
4203
|
});
|
|
3902
4204
|
} catch (error) {
|
|
3903
4205
|
throw new TransientError(
|
|
@@ -3909,6 +4211,9 @@ async function request2(accessToken, path) {
|
|
|
3909
4211
|
const message = `Gmail ${res.status} on ${path}: ${detail}`;
|
|
3910
4212
|
const backoff = res.status === 403 && /rateLimitExceeded|userRateLimitExceeded/i.test(detail);
|
|
3911
4213
|
if (res.status === 429 || res.status >= 500 || backoff) throw new TransientError(message);
|
|
4214
|
+
if (res.status === 403 && /insufficient.*scope|ACCESS_TOKEN_SCOPE_INSUFFICIENT/i.test(detail)) {
|
|
4215
|
+
throw new Error("This Gmail connection cannot send mail. Reconnect Gmail to allow it.");
|
|
4216
|
+
}
|
|
3912
4217
|
throw new Error(message);
|
|
3913
4218
|
}
|
|
3914
4219
|
return await res.json();
|
|
@@ -3928,6 +4233,12 @@ function getMessageHeaders(accessToken, id) {
|
|
|
3928
4233
|
function getMessage(accessToken, id) {
|
|
3929
4234
|
return request2(accessToken, `/messages/${id}?format=full`);
|
|
3930
4235
|
}
|
|
4236
|
+
function sendMessage(accessToken, raw, threadId) {
|
|
4237
|
+
return request2(accessToken, "/messages/send", {
|
|
4238
|
+
method: "POST",
|
|
4239
|
+
body: threadId ? { raw, threadId } : { raw }
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
3931
4242
|
|
|
3932
4243
|
// src/connectors/gmail/mail.ts
|
|
3933
4244
|
function mailRef(id) {
|
|
@@ -4017,12 +4328,31 @@ function mailContext(message) {
|
|
|
4017
4328
|
].join("\n")
|
|
4018
4329
|
};
|
|
4019
4330
|
}
|
|
4331
|
+
function base64url(text3) {
|
|
4332
|
+
return Buffer.from(text3, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
4333
|
+
}
|
|
4334
|
+
function encodedWord(text3) {
|
|
4335
|
+
return /^[\x20-\x7e]*$/.test(text3) ? text3 : `=?UTF-8?B?${Buffer.from(text3, "utf8").toString("base64")}?=`;
|
|
4336
|
+
}
|
|
4337
|
+
function composeMail(input) {
|
|
4338
|
+
const lines = [
|
|
4339
|
+
`To: ${input.to}`,
|
|
4340
|
+
`Subject: ${encodedWord(input.subject)}`,
|
|
4341
|
+
...input.inReplyTo ? [`In-Reply-To: ${input.inReplyTo}`, `References: ${input.inReplyTo}`] : [],
|
|
4342
|
+
"MIME-Version: 1.0",
|
|
4343
|
+
'Content-Type: text/plain; charset="UTF-8"',
|
|
4344
|
+
"Content-Transfer-Encoding: base64",
|
|
4345
|
+
"",
|
|
4346
|
+
Buffer.from(input.body, "utf8").toString("base64").replace(/.{76}/g, "$&\r\n")
|
|
4347
|
+
];
|
|
4348
|
+
return base64url(lines.join("\r\n"));
|
|
4349
|
+
}
|
|
4020
4350
|
|
|
4021
4351
|
// src/connectors/gmail/poll.ts
|
|
4022
4352
|
function baseQuery(workflowId) {
|
|
4023
|
-
const
|
|
4024
|
-
if (!
|
|
4025
|
-
return
|
|
4353
|
+
const trigger = gmailManifest.triggers.find((entry) => entry.id === workflowId);
|
|
4354
|
+
if (!trigger?.watches) throw new Error(`Gmail cannot watch for ${workflowId}.`);
|
|
4355
|
+
return trigger.watches;
|
|
4026
4356
|
}
|
|
4027
4357
|
function boolean2(value, fallback) {
|
|
4028
4358
|
return typeof value === "boolean" ? value : fallback;
|
|
@@ -4237,8 +4567,33 @@ var gmailRuntime = defineRuntime({
|
|
|
4237
4567
|
})
|
|
4238
4568
|
};
|
|
4239
4569
|
},
|
|
4240
|
-
|
|
4241
|
-
|
|
4570
|
+
/**
|
|
4571
|
+
* Answers go to the connected account itself, the way a bot answers
|
|
4572
|
+
* whoever asked it. When the run started from an email in this inbox, the
|
|
4573
|
+
* answer joins that thread.
|
|
4574
|
+
*/
|
|
4575
|
+
async applyAction({ actionId, source, body, credential }) {
|
|
4576
|
+
if (actionId !== "gmail.send") throw new Error(`Gmail cannot ${actionId}.`);
|
|
4577
|
+
const { accessToken } = await usableToken2(credential);
|
|
4578
|
+
const profile = await getProfile(accessToken);
|
|
4579
|
+
let subject = source.title || "From Loopable";
|
|
4580
|
+
let threadId;
|
|
4581
|
+
let inReplyTo;
|
|
4582
|
+
const sourceId = source.connectorId === "gmail" ? source.ref.replace(/^mail#/, "") : "";
|
|
4583
|
+
if (sourceId) {
|
|
4584
|
+
const original = await getMessage(accessToken, sourceId);
|
|
4585
|
+
threadId = original.threadId;
|
|
4586
|
+
inReplyTo = headerOf(original, "Message-ID") || void 0;
|
|
4587
|
+
const said = headerOf(original, "Subject");
|
|
4588
|
+
if (said) subject = /^re:/i.test(said) ? said : `Re: ${said}`;
|
|
4589
|
+
}
|
|
4590
|
+
const sent = await sendMessage(
|
|
4591
|
+
accessToken,
|
|
4592
|
+
composeMail({ to: profile.emailAddress, subject, body, inReplyTo }),
|
|
4593
|
+
threadId
|
|
4594
|
+
);
|
|
4595
|
+
return { url: permalink(sent.id) };
|
|
4596
|
+
},
|
|
4242
4597
|
auth: {
|
|
4243
4598
|
async startAuthorization({ redirectUri, state }) {
|
|
4244
4599
|
const registration = requireRegistration2();
|
|
@@ -4412,8 +4767,9 @@ async function authTest(botToken) {
|
|
|
4412
4767
|
const teamId = typeof body.team_id === "string" ? body.team_id : "";
|
|
4413
4768
|
const botUserId = typeof body.user_id === "string" ? body.user_id : "";
|
|
4414
4769
|
const botId = typeof body.bot_id === "string" ? body.bot_id : "";
|
|
4770
|
+
const botName = typeof body.user === "string" ? body.user : "";
|
|
4415
4771
|
if (!teamId || !botUserId) throw new Error("Slack did not return which workspace this bot belongs to.");
|
|
4416
|
-
return { botToken, teamId, team, teamUrl, botUserId, botId };
|
|
4772
|
+
return { botToken, teamId, team, teamUrl, botUserId, botId, botName };
|
|
4417
4773
|
}
|
|
4418
4774
|
async function listConversations(credential) {
|
|
4419
4775
|
const body = await slack("conversations.list", credential.botToken, {
|
|
@@ -4526,7 +4882,9 @@ function destination(target, source) {
|
|
|
4526
4882
|
function accountOf2(credential) {
|
|
4527
4883
|
return {
|
|
4528
4884
|
id: credential.teamId,
|
|
4529
|
-
|
|
4885
|
+
// The bot, then the workspace it is in: "@loopable · Acme". The workspace
|
|
4886
|
+
// alone read as if it were the bot's name.
|
|
4887
|
+
label: [credential.botName ? `@${credential.botName}` : null, credential.team || credential.teamId].filter(Boolean).join(" \xB7 "),
|
|
4530
4888
|
url: credential.teamUrl || void 0
|
|
4531
4889
|
};
|
|
4532
4890
|
}
|
|
@@ -4791,7 +5149,7 @@ async function replyMessage(credential, messageId, text3) {
|
|
|
4791
5149
|
if (!id) throw new Error("Feishu accepted the reply but did not say where it landed.");
|
|
4792
5150
|
return { id, chatId };
|
|
4793
5151
|
}
|
|
4794
|
-
async function
|
|
5152
|
+
async function sendMessage2(credential, chatId, text3) {
|
|
4795
5153
|
const body = await call(credential.host, "/im/v1/messages", {
|
|
4796
5154
|
token: credential.tenantAccessToken,
|
|
4797
5155
|
method: "POST",
|
|
@@ -4939,7 +5297,7 @@ var feishuRuntime = {
|
|
|
4939
5297
|
const { credential: account2 } = await usable(credential);
|
|
4940
5298
|
const to = destination2(target, source);
|
|
4941
5299
|
const text3 = body.trim();
|
|
4942
|
-
const posted = to.messageId && source.connectorId === "feishu" ? await replyMessage(account2, to.messageId, text3) : await
|
|
5300
|
+
const posted = to.messageId && source.connectorId === "feishu" ? await replyMessage(account2, to.messageId, text3) : await sendMessage2(account2, to.chatId ?? "", text3);
|
|
4943
5301
|
return { url: permalink3(account2.host, posted.chatId || to.chatId || "") };
|
|
4944
5302
|
},
|
|
4945
5303
|
auth: {
|
|
@@ -5130,7 +5488,7 @@ async function getUpdates(credential, cursor) {
|
|
|
5130
5488
|
if (refused) throw new Error(refused);
|
|
5131
5489
|
return { messages: body.msgs ?? [], cursor: body.get_updates_buf || cursor };
|
|
5132
5490
|
}
|
|
5133
|
-
async function
|
|
5491
|
+
async function sendMessage3(credential, input) {
|
|
5134
5492
|
const body = await call2(`${credential.baseUrl}/ilink/bot/sendmessage`, {
|
|
5135
5493
|
method: "POST",
|
|
5136
5494
|
headers: postHeaders(credential.botToken),
|
|
@@ -5279,7 +5637,7 @@ var wechatRuntime = {
|
|
|
5279
5637
|
const account2 = credential;
|
|
5280
5638
|
const to = recipientFor(target, source, account2);
|
|
5281
5639
|
for (const part of chunked(body)) {
|
|
5282
|
-
await
|
|
5640
|
+
await sendMessage3(account2, {
|
|
5283
5641
|
toUserId: to.userId,
|
|
5284
5642
|
contextToken: to.contextToken,
|
|
5285
5643
|
// Unique per piece, so a retry cannot post half an answer twice.
|
|
@@ -7069,7 +7427,7 @@ var SQLiteSelectQueryBuilderBase = class extends TypedQueryBuilder {
|
|
|
7069
7427
|
const baseTableName = this.tableName;
|
|
7070
7428
|
const tableName = getTableLikeName(table);
|
|
7071
7429
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
7072
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
7430
|
+
if (typeof tableName === "string" && this.config.joins?.some((join9) => join9.alias === tableName)) {
|
|
7073
7431
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7074
7432
|
}
|
|
7075
7433
|
if (!this.isPartialSelect) {
|
|
@@ -7928,7 +8286,7 @@ var SQLiteUpdateBase = class extends QueryPromise {
|
|
|
7928
8286
|
createJoin(joinType) {
|
|
7929
8287
|
return (table, on2) => {
|
|
7930
8288
|
const tableName = getTableLikeName(table);
|
|
7931
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
8289
|
+
if (typeof tableName === "string" && this.config.joins.some((join9) => join9.alias === tableName)) {
|
|
7932
8290
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7933
8291
|
}
|
|
7934
8292
|
if (typeof on2 === "function") {
|
|
@@ -9387,6 +9745,10 @@ var runners = sqliteTable("runners", {
|
|
|
9387
9745
|
status: text2("status").$type().notNull().default("offline"),
|
|
9388
9746
|
inventory: text2("inventory", { mode: "json" }).$type().notNull().default([]),
|
|
9389
9747
|
lastSeenAt: integer("last_seen_at", { mode: "timestamp_ms" }),
|
|
9748
|
+
/** The runner's loopable-cli version. Null for runners before 0.3. */
|
|
9749
|
+
version: text2("version"),
|
|
9750
|
+
/** What jobs it understands; see RUNNER_PROTOCOL. Null means the first. */
|
|
9751
|
+
protocol: integer("protocol"),
|
|
9390
9752
|
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
9391
9753
|
});
|
|
9392
9754
|
|
|
@@ -9409,13 +9771,13 @@ function migrationsFolder() {
|
|
|
9409
9771
|
join3(here, "../../../drizzle"),
|
|
9410
9772
|
join3(process.cwd(), "drizzle")
|
|
9411
9773
|
];
|
|
9412
|
-
return candidates2.find((
|
|
9774
|
+
return candidates2.find((folder) => existsSync2(folder)) ?? join3(process.cwd(), "drizzle");
|
|
9413
9775
|
}
|
|
9414
9776
|
async function migrateIfNeeded() {
|
|
9415
9777
|
if (process.env.LOOPABLE_SKIP_MIGRATE === "1") return;
|
|
9416
|
-
const
|
|
9417
|
-
if (!existsSync2(
|
|
9418
|
-
await migrate(db(), { migrationsFolder:
|
|
9778
|
+
const folder = migrationsFolder();
|
|
9779
|
+
if (!existsSync2(folder)) return;
|
|
9780
|
+
await migrate(db(), { migrationsFolder: folder });
|
|
9419
9781
|
}
|
|
9420
9782
|
function rowsChanged(result) {
|
|
9421
9783
|
return Number(result.rowsAffected ?? result.changes ?? 0);
|
|
@@ -9532,14 +9894,14 @@ async function decrypt(blob2) {
|
|
|
9532
9894
|
return new TextDecoder().decode(opened);
|
|
9533
9895
|
}
|
|
9534
9896
|
function bytesToBase64(bytes) {
|
|
9535
|
-
let
|
|
9536
|
-
for (const byte of bytes)
|
|
9537
|
-
return btoa(
|
|
9897
|
+
let binary4 = "";
|
|
9898
|
+
for (const byte of bytes) binary4 += String.fromCharCode(byte);
|
|
9899
|
+
return btoa(binary4);
|
|
9538
9900
|
}
|
|
9539
9901
|
function base64ToBytes(blob2) {
|
|
9540
|
-
const
|
|
9541
|
-
const bytes = new Uint8Array(
|
|
9542
|
-
for (let i = 0; i <
|
|
9902
|
+
const binary4 = atob(blob2);
|
|
9903
|
+
const bytes = new Uint8Array(binary4.length);
|
|
9904
|
+
for (let i = 0; i < binary4.length; i += 1) bytes[i] = binary4.charCodeAt(i);
|
|
9543
9905
|
return bytes;
|
|
9544
9906
|
}
|
|
9545
9907
|
var tableStore = {
|
|
@@ -9657,12 +10019,13 @@ async function checkConnection(id) {
|
|
|
9657
10019
|
// src/server/tasks.ts
|
|
9658
10020
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
9659
10021
|
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, rmSync as rmSync3, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9660
|
-
import {
|
|
10022
|
+
import { join as join7 } from "node:path";
|
|
9661
10023
|
|
|
9662
10024
|
// src/agents/manifests.ts
|
|
9663
10025
|
init_manifest();
|
|
9664
10026
|
init_manifest2();
|
|
9665
|
-
|
|
10027
|
+
init_manifest3();
|
|
10028
|
+
var AGENT_MANIFESTS = [codexManifest, claudeManifest, cursorAgentManifest];
|
|
9666
10029
|
function agentManifest(id) {
|
|
9667
10030
|
return AGENT_MANIFESTS.find((manifest) => manifest.id === id);
|
|
9668
10031
|
}
|
|
@@ -9689,6 +10052,10 @@ var REVIEW_FORMAT = [
|
|
|
9689
10052
|
`- "findings" may be empty when the summary says everything worth saying.`
|
|
9690
10053
|
].join("\n");
|
|
9691
10054
|
|
|
10055
|
+
// src/server/tasks.ts
|
|
10056
|
+
init_agent_job();
|
|
10057
|
+
init_version();
|
|
10058
|
+
|
|
9692
10059
|
// src/server/agents.ts
|
|
9693
10060
|
init_runtimes();
|
|
9694
10061
|
var DEFAULT_AGENT_KEY = "defaultAgentId";
|
|
@@ -9760,7 +10127,11 @@ function branchFor(input) {
|
|
|
9760
10127
|
|
|
9761
10128
|
// src/server/runners.ts
|
|
9762
10129
|
import { hostname as osHostname } from "node:os";
|
|
10130
|
+
init_version();
|
|
9763
10131
|
var ONLINE_MS = 45e3;
|
|
10132
|
+
function protocolOf(row) {
|
|
10133
|
+
return row.protocol ?? LEGACY_RUNNER_PROTOCOL;
|
|
10134
|
+
}
|
|
9764
10135
|
function isRunnerOnline(row, now = Date.now()) {
|
|
9765
10136
|
if (row.status !== "online") return false;
|
|
9766
10137
|
if (!row.lastSeenAt) return false;
|
|
@@ -9774,7 +10145,10 @@ function toView2(row, now = Date.now()) {
|
|
|
9774
10145
|
hostname: row.hostname,
|
|
9775
10146
|
status: online ? "online" : "offline",
|
|
9776
10147
|
inventory: row.inventory,
|
|
9777
|
-
lastSeenAt: row.lastSeenAt?.toISOString() ?? null
|
|
10148
|
+
lastSeenAt: row.lastSeenAt?.toISOString() ?? null,
|
|
10149
|
+
version: row.version,
|
|
10150
|
+
protocol: protocolOf(row),
|
|
10151
|
+
outdated: protocolOf(row) < RUNNER_PROTOCOL || olderThan(row.version, VERSION)
|
|
9778
10152
|
};
|
|
9779
10153
|
}
|
|
9780
10154
|
async function listRunners() {
|
|
@@ -9798,11 +10172,13 @@ async function busyRunnerIds() {
|
|
|
9798
10172
|
)
|
|
9799
10173
|
);
|
|
9800
10174
|
}
|
|
9801
|
-
async function availableAgentIds(requiresHost = false) {
|
|
10175
|
+
async function availableAgentIds(requiresHost = false, minProtocol = 0) {
|
|
9802
10176
|
const local = osHostname();
|
|
9803
10177
|
return [
|
|
9804
10178
|
...new Set(
|
|
9805
|
-
(await listRunners()).filter(
|
|
10179
|
+
(await listRunners()).filter(
|
|
10180
|
+
(row) => row.status === "online" && (!requiresHost || row.hostname === local) && row.protocol >= minProtocol
|
|
10181
|
+
).flatMap(
|
|
9806
10182
|
(row) => row.inventory.filter((entry) => entry.installed && entry.signedIn).map((entry) => entry.agentId)
|
|
9807
10183
|
)
|
|
9808
10184
|
)
|
|
@@ -9811,9 +10187,11 @@ async function availableAgentIds(requiresHost = false) {
|
|
|
9811
10187
|
async function pickRunner(input) {
|
|
9812
10188
|
const now = Date.now();
|
|
9813
10189
|
const local = osHostname();
|
|
9814
|
-
|
|
10190
|
+
const minProtocol = input.minProtocol ?? 0;
|
|
10191
|
+
const capable = (await db().select().from(runners).all()).filter(
|
|
9815
10192
|
(row) => isRunnerOnline(row, now) && runnerCanRun(row, input.agentId)
|
|
9816
10193
|
);
|
|
10194
|
+
let candidates2 = capable.filter((row) => protocolOf(row) >= minProtocol);
|
|
9817
10195
|
if (input.requiresHost) {
|
|
9818
10196
|
candidates2 = candidates2.filter((row) => row.hostname === local);
|
|
9819
10197
|
}
|
|
@@ -9824,6 +10202,11 @@ async function pickRunner(input) {
|
|
|
9824
10202
|
if (input.requiresHost) {
|
|
9825
10203
|
throw new Error("This job needs a runner on the same host as Loopable.");
|
|
9826
10204
|
}
|
|
10205
|
+
if (capable.length > 0) {
|
|
10206
|
+
throw new Error(
|
|
10207
|
+
"Every runner with this agent is too old for this job. Upgrade them with npm install -g loopable-cli."
|
|
10208
|
+
);
|
|
10209
|
+
}
|
|
9827
10210
|
throw new Error("No runner is online that can run this agent.");
|
|
9828
10211
|
}
|
|
9829
10212
|
|
|
@@ -9965,11 +10348,11 @@ async function enqueueSignal(input) {
|
|
|
9965
10348
|
});
|
|
9966
10349
|
}
|
|
9967
10350
|
function sweepRunDirs(maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
9968
|
-
const root =
|
|
10351
|
+
const root = join7(dataDir(), "runs");
|
|
9969
10352
|
if (!existsSync4(root)) return 0;
|
|
9970
10353
|
let removed = 0;
|
|
9971
10354
|
for (const entry of readdirSync(root)) {
|
|
9972
|
-
const dir =
|
|
10355
|
+
const dir = join7(root, entry);
|
|
9973
10356
|
try {
|
|
9974
10357
|
if (Date.now() - statSync(dir).mtimeMs < maxAgeMs) continue;
|
|
9975
10358
|
rmSync3(dir, { recursive: true, force: true });
|
|
@@ -10032,7 +10415,7 @@ async function preparePromptTask(id, signal) {
|
|
|
10032
10415
|
let logPath = null;
|
|
10033
10416
|
if (!isHosted()) {
|
|
10034
10417
|
const workspace = runDir(id);
|
|
10035
|
-
logPath =
|
|
10418
|
+
logPath = join7(workspace, "agent.log");
|
|
10036
10419
|
writeFileSync2(logPath, "", { flag: "a" });
|
|
10037
10420
|
}
|
|
10038
10421
|
const runnerId = await pickRunner({ agentId: task.agentId, requiresHost: false });
|
|
@@ -10056,14 +10439,17 @@ async function prepareTask(id, signal) {
|
|
|
10056
10439
|
if (!isHosted()) {
|
|
10057
10440
|
const workspace = runDir(id);
|
|
10058
10441
|
for (const file of item.context) {
|
|
10059
|
-
writeFileSync2(
|
|
10442
|
+
writeFileSync2(join7(workspace, file.name), file.body);
|
|
10060
10443
|
}
|
|
10061
|
-
logPath =
|
|
10444
|
+
logPath = join7(workspace, "agent.log");
|
|
10062
10445
|
writeFileSync2(logPath, "", { flag: "a" });
|
|
10063
10446
|
}
|
|
10447
|
+
const folder = workflow.runsIn === "folder" && typeof loop.settings.folder === "string" ? loop.settings.folder : null;
|
|
10064
10448
|
const runnerId = await pickRunner({
|
|
10065
10449
|
agentId,
|
|
10066
|
-
requiresHost:
|
|
10450
|
+
requiresHost: folder !== null && folderIsLegacy(folder),
|
|
10451
|
+
// A relative folder needs a runner that resolves it under its own home.
|
|
10452
|
+
minProtocol: folder !== null && !folderIsLegacy(folder) ? FOLDER_JOB_PROTOCOL : 0
|
|
10067
10453
|
});
|
|
10068
10454
|
await updateTask(id, {
|
|
10069
10455
|
state: "awaiting_agent",
|
|
@@ -10238,7 +10624,7 @@ async function pollLoop(loop, claimed) {
|
|
|
10238
10624
|
if (!runtime.poll) throw new Error(`${manifest.name} cannot watch for anything yet.`);
|
|
10239
10625
|
const { credential } = await credentialForConnector(loop.connectorId);
|
|
10240
10626
|
const answer = await runtime.poll({
|
|
10241
|
-
workflowId:
|
|
10627
|
+
workflowId: workflow.triggerId,
|
|
10242
10628
|
settings: loop.settings,
|
|
10243
10629
|
credential,
|
|
10244
10630
|
cursor: loop.pollCursor ?? null
|
|
@@ -10258,7 +10644,7 @@ async function pollLoop(loop, claimed) {
|
|
|
10258
10644
|
signals: fresh,
|
|
10259
10645
|
agentId: await defaultAgentFor(loop.agentId),
|
|
10260
10646
|
guidance: loop.guidance,
|
|
10261
|
-
cwd: mkdtempSync(
|
|
10647
|
+
cwd: mkdtempSync(join8(tmpdir(), "loopable-triage-"))
|
|
10262
10648
|
});
|
|
10263
10649
|
}
|
|
10264
10650
|
for (const signal of found) {
|