loopable-cli 0.1.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 +17 -0
- package/.output/public/apple-touch-icon.png +0 -0
- package/.output/public/assets/_connectorId-BWjYn5tm.js +1 -0
- package/.output/public/assets/_loopId-Ds4RkyEc.js +1 -0
- package/.output/public/assets/_taskId-BOzICXDf.js +1 -0
- package/.output/public/assets/alert-Bn9-Zm0l.js +1 -0
- package/.output/public/assets/arrow-left-BF9juV9T.js +1 -0
- package/.output/public/assets/arrow-right-CzqvnOY1.js +1 -0
- package/.output/public/assets/button-HCY6DCY-.js +1 -0
- package/.output/public/assets/connection-status-CYZB8kLy.js +1 -0
- package/.output/public/assets/connector-icon-BZ_MUJV_.js +1 -0
- package/.output/public/assets/connectors-BvzttfSu.js +1 -0
- package/.output/public/assets/connectors-DdJNwRN_.js +1 -0
- package/.output/public/assets/external-link-D46X-bXa.js +1 -0
- package/.output/public/assets/gaps-D-I6xiDc.js +1 -0
- package/.output/public/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/.output/public/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/.output/public/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/.output/public/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/.output/public/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/.output/public/assets/inbox-BUUu9xxE.js +1 -0
- package/.output/public/assets/index-ztzex0FN.js +10 -0
- package/.output/public/assets/loader-CcBl2u8m.js +1 -0
- package/.output/public/assets/loop-form-dB3CLcZb.js +1 -0
- package/.output/public/assets/loops-DRunQrxL.js +1 -0
- package/.output/public/assets/loops-oSdqMd29.js +1 -0
- package/.output/public/assets/manifests-DNMqf7Vi.js +7 -0
- package/.output/public/assets/new-uyFxVjuB.js +1 -0
- package/.output/public/assets/preload-helper-ORbuV86A.js +3 -0
- package/.output/public/assets/runners-DSU3r5Ww.js +1 -0
- package/.output/public/assets/setting-fields-ixkpdBb-.js +3 -0
- package/.output/public/assets/styles-DZL5V1o7.css +2 -0
- package/.output/public/assets/switch-d5ummmHY.js +1 -0
- package/.output/public/assets/task-list-BpOStt9v.js +1 -0
- package/.output/public/assets/tasks-B8MsKgic.js +1 -0
- package/.output/public/assets/trash-2-D6u6NpzO.js +1 -0
- package/.output/public/favicon.svg +9 -0
- package/.output/server/_chunks/ssr-renderer.mjs +26 -0
- package/.output/server/_connectorId-8nb5yOzG.mjs +421 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +12841 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +14349 -0
- package/.output/server/_libs/class-variance-authority+clsx.mjs +69 -0
- package/.output/server/_libs/dijkstrajs.mjs +118 -0
- package/.output/server/_libs/drizzle-orm.mjs +4349 -0
- package/.output/server/_libs/h3+rou3+srvx.mjs +1361 -0
- package/.output/server/_libs/lucide-react.mjs +911 -0
- package/.output/server/_libs/next-themes.mjs +53 -0
- package/.output/server/_libs/pngjs.mjs +1680 -0
- package/.output/server/_libs/qrcode.mjs +2583 -0
- package/.output/server/_libs/sonner.mjs +955 -0
- package/.output/server/_libs/tailwind-merge.mjs +3380 -0
- package/.output/server/_libs/tanstack__history.mjs +341 -0
- package/.output/server/_libs/ufo.mjs +64 -0
- package/.output/server/_loopId-BsUL1SeL.mjs +272 -0
- package/.output/server/_runtime.mjs +35 -0
- package/.output/server/_ssr/access-zIl15o8y.mjs +42 -0
- package/.output/server/_ssr/agents-Btql102J.mjs +657 -0
- package/.output/server/_ssr/agents-DAOZPgO2.mjs +76 -0
- package/.output/server/_ssr/alert-NEiOCTOl.mjs +37 -0
- package/.output/server/_ssr/callback-CUINvl7M.mjs +62 -0
- package/.output/server/_ssr/client-CKkOfh-s.mjs +335 -0
- package/.output/server/_ssr/connection-status-Bi5peUAk.mjs +31 -0
- package/.output/server/_ssr/connections-qnw2GVlP.mjs +2542 -0
- package/.output/server/_ssr/connector-icon-DZXQLLFo.mjs +37 -0
- package/.output/server/_ssr/connectors-BHNwuIMj.mjs +106 -0
- package/.output/server/_ssr/connectors-C28g0i4c.mjs +83 -0
- package/.output/server/_ssr/createServerRpc-DPDrnFdH.mjs +12 -0
- package/.output/server/_ssr/empty-plugin-adapters-BJ4m9L8g.mjs +5 -0
- package/.output/server/_ssr/gaps-RAFSXLZf.mjs +53 -0
- package/.output/server/_ssr/inbox-k5zHergU.mjs +35 -0
- package/.output/server/_ssr/loop-form-DlObSewZ.mjs +412 -0
- package/.output/server/_ssr/loops-APFcP0y6.mjs +311 -0
- package/.output/server/_ssr/loops-xt2Bn7_q.mjs +222 -0
- package/.output/server/_ssr/new-DfKyd2Wa.mjs +169 -0
- package/.output/server/_ssr/router-3WmpE5b_.mjs +1553 -0
- package/.output/server/_ssr/runners-By-mwc3F.mjs +32 -0
- package/.output/server/_ssr/runners-DI_NxpHC.mjs +116 -0
- package/.output/server/_ssr/runners-DtXs4Q8a.mjs +106 -0
- package/.output/server/_ssr/secrets-Hqti-niS.mjs +199 -0
- package/.output/server/_ssr/setting-fields-8vKFj4df.mjs +92 -0
- package/.output/server/_ssr/ssr.mjs +6605 -0
- package/.output/server/_ssr/start-Bdc2GJUI.mjs +51 -0
- package/.output/server/_ssr/switch-ReNglgH6.mjs +18 -0
- package/.output/server/_ssr/task-list-CUITIqkb.mjs +186 -0
- package/.output/server/_ssr/tasks-SAjNovX6.mjs +110 -0
- package/.output/server/_ssr/tasks-vcoodp_D.mjs +533 -0
- package/.output/server/_tanstack-start-manifest_v-C0cP5F0M.mjs +125 -0
- package/.output/server/_taskId-CXS3lod0.mjs +249 -0
- package/.output/server/index.mjs +541 -0
- package/README.md +81 -0
- package/dist/cli.mjs +165 -0
- package/dist/dispatcher.mjs +9871 -0
- package/dist/runner.mjs +657 -0
- package/drizzle/0000_cold_demogoblin.sql +29 -0
- package/drizzle/0001_fancy_blonde_phantom.sql +13 -0
- package/drizzle/0002_spooky_smiling_tiger.sql +16 -0
- package/drizzle/0003_small_golden_guardian.sql +25 -0
- package/drizzle/0004_wonderful_thunderbolts.sql +39 -0
- package/drizzle/0005_named_workflows.sql +7 -0
- package/drizzle/0006_signals.sql +18 -0
- package/drizzle/0007_review_comments.sql +1 -0
- package/drizzle/0008_held_signals.sql +1 -0
- package/drizzle/0009_work_item_ref.sql +58 -0
- package/drizzle/0010_signal_payload.sql +2 -0
- package/drizzle/0011_rule_poll_cursor.sql +1 -0
- package/drizzle/0012_loops.sql +63 -0
- package/drizzle/0013_loop_owns_action_and_prompt.sql +154 -0
- package/drizzle/0014_loop_poll_every.sql +4 -0
- package/drizzle/0015_machines.sql +10 -0
- package/drizzle/0016_task_machine.sql +1 -0
- package/drizzle/0017_loop_machine.sql +1 -0
- package/drizzle/0018_runners.sql +1 -0
- package/drizzle/0019_task_runner.sql +1 -0
- package/drizzle/0020_loop_runner.sql +1 -0
- package/drizzle/0021_drop_runner_kind.sql +1 -0
- package/drizzle/0022_drop_loop_runner.sql +1 -0
- package/drizzle/0023_shared_host.sql +6 -0
- package/drizzle/meta/0000_snapshot.json +206 -0
- package/drizzle/meta/0001_snapshot.json +284 -0
- package/drizzle/meta/0002_snapshot.json +390 -0
- package/drizzle/meta/0003_snapshot.json +566 -0
- package/drizzle/meta/0004_snapshot.json +633 -0
- package/drizzle/meta/0005_snapshot.json +626 -0
- package/drizzle/meta/0006_snapshot.json +752 -0
- package/drizzle/meta/0007_snapshot.json +759 -0
- package/drizzle/meta/0008_snapshot.json +766 -0
- package/drizzle/meta/0009_snapshot.json +752 -0
- package/drizzle/meta/0010_snapshot.json +766 -0
- package/drizzle/meta/0011_snapshot.json +773 -0
- package/drizzle/meta/0012_snapshot.json +773 -0
- package/drizzle/meta/0013_snapshot.json +817 -0
- package/drizzle/meta/_journal.json +174 -0
- package/package.json +88 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { n as createServerFn } from "./ssr.mjs";
|
|
2
|
+
import { a as setDefaultAgent, i as saveAgentSettings, r as listAgents, s as testAgent } from "./agents-Btql102J.mjs";
|
|
3
|
+
import { c as listRunners, t as availableAgentIds } from "./runners-DtXs4Q8a.mjs";
|
|
4
|
+
import { t as createServerRpc } from "./createServerRpc-DPDrnFdH.mjs";
|
|
5
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/agents-DAOZPgO2.js
|
|
6
|
+
async function withRunners(agents) {
|
|
7
|
+
const runners = await listRunners();
|
|
8
|
+
const available = await availableAgentIds();
|
|
9
|
+
const stored = agents.find((agent) => agent.isDefault)?.agentId ?? null;
|
|
10
|
+
const implicit = !stored && available.length === 1 ? available[0] : null;
|
|
11
|
+
return agents.map((agent) => {
|
|
12
|
+
const onRunners = runners.flatMap((runner) => {
|
|
13
|
+
const entry = runner.inventory.find((item) => item.agentId === agent.agentId);
|
|
14
|
+
if (!entry?.installed) return [];
|
|
15
|
+
return [{
|
|
16
|
+
runnerId: runner.id,
|
|
17
|
+
name: runner.name,
|
|
18
|
+
status: runner.status,
|
|
19
|
+
signedIn: entry.signedIn,
|
|
20
|
+
version: entry.version
|
|
21
|
+
}];
|
|
22
|
+
});
|
|
23
|
+
const isDefault = agent.agentId === stored || agent.agentId === implicit;
|
|
24
|
+
return {
|
|
25
|
+
...agent,
|
|
26
|
+
onRunners,
|
|
27
|
+
isDefault,
|
|
28
|
+
defaultIsImplicit: isDefault && agent.agentId === implicit
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
var getAgentsPage_createServerFn_handler = createServerRpc({
|
|
33
|
+
id: "bde588e9b0b81a4ee619a63872d6b8b76e22db006c7a3c4351bcd56e5f76bbed",
|
|
34
|
+
name: "getAgentsPage",
|
|
35
|
+
filename: "src/server/functions/agents.ts"
|
|
36
|
+
}, (opts) => getAgentsPage.__executeServer(opts));
|
|
37
|
+
var getAgentsPage = createServerFn({ method: "GET" }).handler(getAgentsPage_createServerFn_handler, async () => ({ agents: await withRunners(await listAgents()) }));
|
|
38
|
+
var saveAgent_createServerFn_handler = createServerRpc({
|
|
39
|
+
id: "80883b912a5a0aba2aa583b505e4e289d765a3f7fbb793a97692f439e7f960ba",
|
|
40
|
+
name: "saveAgent",
|
|
41
|
+
filename: "src/server/functions/agents.ts"
|
|
42
|
+
}, (opts) => saveAgent.__executeServer(opts));
|
|
43
|
+
var saveAgent = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(saveAgent_createServerFn_handler, async ({ data }) => {
|
|
44
|
+
await saveAgentSettings(data.agentId, {
|
|
45
|
+
permissionMode: data.permissionMode,
|
|
46
|
+
model: data.model,
|
|
47
|
+
timeoutMs: data.timeoutMs
|
|
48
|
+
});
|
|
49
|
+
return await withRunners(await listAgents());
|
|
50
|
+
});
|
|
51
|
+
var chooseDefaultAgent_createServerFn_handler = createServerRpc({
|
|
52
|
+
id: "143ea51f7421d9eebec9ad31c2df8eed5aa2e5d8035473dbfad39913fd7d54c0",
|
|
53
|
+
name: "chooseDefaultAgent",
|
|
54
|
+
filename: "src/server/functions/agents.ts"
|
|
55
|
+
}, (opts) => chooseDefaultAgent.__executeServer(opts));
|
|
56
|
+
var chooseDefaultAgent = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(chooseDefaultAgent_createServerFn_handler, async ({ data }) => {
|
|
57
|
+
await setDefaultAgent(data.agentId);
|
|
58
|
+
return await withRunners(await listAgents());
|
|
59
|
+
});
|
|
60
|
+
var runAgentTest_createServerFn_handler = createServerRpc({
|
|
61
|
+
id: "b0878f841b35f46b5f2dc9cbd71c5be5895ae1b8903b3b24c24dfcf86291d3e4",
|
|
62
|
+
name: "runAgentTest",
|
|
63
|
+
filename: "src/server/functions/agents.ts"
|
|
64
|
+
}, (opts) => runAgentTest.__executeServer(opts));
|
|
65
|
+
var runAgentTest = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(runAgentTest_createServerFn_handler, async ({ data }) => {
|
|
66
|
+
const result = await testAgent(data.agentId);
|
|
67
|
+
return {
|
|
68
|
+
passed: result.passed,
|
|
69
|
+
output: result.output.slice(0, 2e3),
|
|
70
|
+
detail: result.detail ?? null,
|
|
71
|
+
durationMs: result.durationMs,
|
|
72
|
+
command: result.command
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
//#endregion
|
|
76
|
+
export { chooseDefaultAgent_createServerFn_handler, getAgentsPage_createServerFn_handler, runAgentTest_createServerFn_handler, saveAgent_createServerFn_handler };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "../_runtime.mjs";
|
|
2
|
+
import { U as require_jsx_runtime, W as require_react } from "../_libs/@base-ui/react+[...].mjs";
|
|
3
|
+
import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
|
|
4
|
+
import { V as cn } from "./router-3WmpE5b_.mjs";
|
|
5
|
+
require_react();
|
|
6
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
7
|
+
var alertVariants = cva("group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", {
|
|
8
|
+
variants: { variant: {
|
|
9
|
+
default: "bg-card text-card-foreground",
|
|
10
|
+
destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current"
|
|
11
|
+
} },
|
|
12
|
+
defaultVariants: { variant: "default" }
|
|
13
|
+
});
|
|
14
|
+
function Alert({ className, variant, ...props }) {
|
|
15
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
16
|
+
"data-slot": "alert",
|
|
17
|
+
role: "alert",
|
|
18
|
+
className: cn(alertVariants({ variant }), className),
|
|
19
|
+
...props
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function AlertTitle({ className, ...props }) {
|
|
23
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
24
|
+
"data-slot": "alert-title",
|
|
25
|
+
className: cn("font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", className),
|
|
26
|
+
...props
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function AlertDescription({ className, ...props }) {
|
|
30
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
31
|
+
"data-slot": "alert-description",
|
|
32
|
+
className: cn("text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4", className),
|
|
33
|
+
...props
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { AlertDescription as n, AlertTitle as r, Alert as t };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/callback-CUINvl7M.js
|
|
2
|
+
function configuredOrigin() {
|
|
3
|
+
return process.env.LOOPABLE_BASE_URL?.replace(/\/$/, "") || null;
|
|
4
|
+
}
|
|
5
|
+
function isLoopbackHost(hostname) {
|
|
6
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* RFC1918, link-local, and loopback. A LAN App can use these as the join URL;
|
|
10
|
+
* GitHub and Google will not take them as an OAuth redirect.
|
|
11
|
+
*/
|
|
12
|
+
function isPrivateHostname(hostname) {
|
|
13
|
+
if (isLoopbackHost(hostname)) return true;
|
|
14
|
+
const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
15
|
+
if (v4) {
|
|
16
|
+
const a = Number(v4[1]);
|
|
17
|
+
const b = Number(v4[2]);
|
|
18
|
+
if (a === 10) return true;
|
|
19
|
+
if (a === 127) return true;
|
|
20
|
+
if (a === 192 && b === 168) return true;
|
|
21
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
22
|
+
if (a === 169 && b === 254) return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const host = hostname.toLowerCase();
|
|
26
|
+
if (host.includes(":")) {
|
|
27
|
+
if (host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd")) return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/** Public https origin for cloud. LAN and loopback BASE_URL do not count. */
|
|
32
|
+
function publicConfiguredOrigin() {
|
|
33
|
+
const base = configuredOrigin();
|
|
34
|
+
if (!base) return null;
|
|
35
|
+
try {
|
|
36
|
+
if (isPrivateHostname(new URL(base).hostname)) return null;
|
|
37
|
+
return base;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Where GitHub / Gmail send the browser back. Local is always loopback. */
|
|
43
|
+
function appOrigin(requestUrl) {
|
|
44
|
+
const hosted = publicConfiguredOrigin();
|
|
45
|
+
if (hosted) return hosted;
|
|
46
|
+
const url = new URL(requestUrl);
|
|
47
|
+
return `http://127.0.0.1${url.port ? `:${url.port}` : ""}`;
|
|
48
|
+
}
|
|
49
|
+
function callbackUrl(requestUrl, connectorId) {
|
|
50
|
+
return `${appOrigin(requestUrl)}/api/connectors/${connectorId}/callback`;
|
|
51
|
+
}
|
|
52
|
+
function registrableCallbackUrl(connectorId) {
|
|
53
|
+
const hosted = publicConfiguredOrigin();
|
|
54
|
+
if (hosted) return `${hosted}/api/connectors/${connectorId}/callback`;
|
|
55
|
+
return `http://127.0.0.1/api/connectors/${connectorId}/callback`;
|
|
56
|
+
}
|
|
57
|
+
/** Where runners and people should address this Loopable. May be a LAN IP. */
|
|
58
|
+
function loopableOrigin() {
|
|
59
|
+
return configuredOrigin() ?? "http://127.0.0.1:4321";
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { registrableCallbackUrl as a, loopableOrigin as i, callbackUrl as n, isLoopbackHost as r, appOrigin as t };
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { a as uniqueIndex, c as integer, i as index, n as drizzle, o as sqliteTable, r as primaryKey, s as text, t as migrate, v as sql } from "../_libs/drizzle-orm.mjs";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { createClient } from "@libsql/client";
|
|
7
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/client-CKkOfh-s.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
function isDevProfile() {
|
|
19
|
+
return process.env.LOOPABLE_DEV === "1";
|
|
20
|
+
}
|
|
21
|
+
function dataDir() {
|
|
22
|
+
const dir = process.env.LOOPABLE_HOME ?? join(homedir(), isDevProfile() ? ".loopable-dev" : ".loopable");
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
function dbPath() {
|
|
27
|
+
return process.env.LOOPABLE_DB ?? join(dataDir(), "loopable.sqlite");
|
|
28
|
+
}
|
|
29
|
+
/** Keychain service prefix. Dev must not read or write the deploy install's secrets. */
|
|
30
|
+
function secretNamespace() {
|
|
31
|
+
return process.env.LOOPABLE_SECRET_PREFIX ?? (isDevProfile() ? "loopable-dev" : "loopable");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Where a run keeps its scratch files and its log. Not the system temp
|
|
35
|
+
* directory: the dispatcher writes these and the app reads them, they are the
|
|
36
|
+
* first thing to look at when a run goes wrong, and temp gets swept.
|
|
37
|
+
*/
|
|
38
|
+
function runDir(taskId) {
|
|
39
|
+
const dir = join(dataDir(), "runs", taskId);
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
return dir;
|
|
42
|
+
}
|
|
43
|
+
var schema_exports = /* @__PURE__ */ __exportAll({
|
|
44
|
+
SIGNAL_OUTCOME: () => SIGNAL_OUTCOME,
|
|
45
|
+
agentSettings: () => agentSettings,
|
|
46
|
+
appSettings: () => appSettings,
|
|
47
|
+
authAttempts: () => authAttempts,
|
|
48
|
+
connections: () => connections,
|
|
49
|
+
loops: () => loops,
|
|
50
|
+
runners: () => runners,
|
|
51
|
+
secrets: () => secrets,
|
|
52
|
+
signals: () => signals,
|
|
53
|
+
tasks: () => tasks
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* One row per connected account, so a connector can be connected several times
|
|
57
|
+
* (two GitHub accounts, or github.com alongside an Enterprise host). Credentials
|
|
58
|
+
* live in the keychain under the row id, never in this table.
|
|
59
|
+
*/
|
|
60
|
+
var connections = sqliteTable("connections", {
|
|
61
|
+
id: text("id").primaryKey(),
|
|
62
|
+
connectorId: text("connector_id").notNull(),
|
|
63
|
+
status: text("status").$type().notNull().default("connected"),
|
|
64
|
+
accountId: text("account_id").notNull(),
|
|
65
|
+
accountLabel: text("account_label").notNull(),
|
|
66
|
+
accountUrl: text("account_url"),
|
|
67
|
+
avatarUrl: text("avatar_url"),
|
|
68
|
+
scopes: text("scopes"),
|
|
69
|
+
settings: text("settings", { mode: "json" }).$type().notNull().default({}),
|
|
70
|
+
cursor: text("cursor", { mode: "json" }).$type(),
|
|
71
|
+
lastSyncedAt: integer("last_synced_at", { mode: "timestamp_ms" }),
|
|
72
|
+
lastError: text("last_error"),
|
|
73
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
|
|
74
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
75
|
+
}, (table) => [uniqueIndex("connections_account_unique").on(table.connectorId, table.accountId), index("connections_connector_idx").on(table.connectorId)]);
|
|
76
|
+
/**
|
|
77
|
+
* Short-lived state for an authorization that is mid-flight: the CSRF state and
|
|
78
|
+
* the PKCE verifier that the callback must present. Rows are consumed on
|
|
79
|
+
* callback and swept after they expire.
|
|
80
|
+
*/
|
|
81
|
+
var authAttempts = sqliteTable("auth_attempts", {
|
|
82
|
+
state: text("state").primaryKey(),
|
|
83
|
+
connectorId: text("connector_id").notNull(),
|
|
84
|
+
verifier: text("verifier").notNull(),
|
|
85
|
+
redirectUri: text("redirect_uri").notNull(),
|
|
86
|
+
returnTo: text("return_to"),
|
|
87
|
+
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
88
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
89
|
+
});
|
|
90
|
+
/**
|
|
91
|
+
* One run of one loop against one thing. Kept whether it wrote anything or
|
|
92
|
+
* not, because a loop that runs on its own is only trustworthy if you can see
|
|
93
|
+
* afterwards what it did.
|
|
94
|
+
*/
|
|
95
|
+
var tasks = sqliteTable("tasks", {
|
|
96
|
+
id: text("id").primaryKey(),
|
|
97
|
+
loopId: text("loop_id").notNull().references(() => loops.id, { onDelete: "cascade" }),
|
|
98
|
+
connectorId: text("connector_id").notNull(),
|
|
99
|
+
state: text("state").$type().notNull().default("queued"),
|
|
100
|
+
/** What the task is about, resolved once so the record survives the source. */
|
|
101
|
+
sourceUrl: text("source_url").notNull(),
|
|
102
|
+
sourceKind: text("source_kind").$type().notNull(),
|
|
103
|
+
/** Names the thing inside its own connector. See `WorkItemRef`. */
|
|
104
|
+
sourceRef: text("source_ref").notNull(),
|
|
105
|
+
/** What the connector kept, when the source cannot be read twice. */
|
|
106
|
+
sourcePayload: text("source_payload", { mode: "json" }).$type(),
|
|
107
|
+
/**
|
|
108
|
+
* Identifying a link needs no network, but the title does, so it arrives
|
|
109
|
+
* when the dispatcher fetches rather than when the task is queued.
|
|
110
|
+
*/
|
|
111
|
+
sourceTitle: text("source_title"),
|
|
112
|
+
/** Set when a run was asked to stop before writing anything. */
|
|
113
|
+
dryRun: integer("dry_run", { mode: "boolean" }).notNull().default(false),
|
|
114
|
+
agentId: text("agent_id"),
|
|
115
|
+
runnerId: text("runner_id"),
|
|
116
|
+
agentCommand: text("agent_command"),
|
|
117
|
+
/** What the agent produced, which is also what gets written back. */
|
|
118
|
+
output: text("output"),
|
|
119
|
+
/**
|
|
120
|
+
* Findings that were checked against the diff and can be attached to a
|
|
121
|
+
* line. Kept because a write that failed on a bad connection has to be
|
|
122
|
+
* repeatable without paying for the review a second time.
|
|
123
|
+
*/
|
|
124
|
+
comments: text("comments", { mode: "json" }).$type(),
|
|
125
|
+
/**
|
|
126
|
+
* Which action is going to write this and where, taken from the loop when
|
|
127
|
+
* the task was queued. Kept here rather than read back off the loop so
|
|
128
|
+
* that changing a loop cannot change what a task already in the queue is
|
|
129
|
+
* about to do.
|
|
130
|
+
*/
|
|
131
|
+
actionConnectorId: text("action_connector_id").notNull(),
|
|
132
|
+
actionId: text("action_id").notNull(),
|
|
133
|
+
actionTarget: text("action_target", { mode: "json" }).$type().notNull().default({}),
|
|
134
|
+
/** Where the write landed, once it has. */
|
|
135
|
+
resultUrl: text("result_url"),
|
|
136
|
+
error: text("error"),
|
|
137
|
+
/** Everything the agent printed, streamed to disk while it runs. */
|
|
138
|
+
logPath: text("log_path"),
|
|
139
|
+
/** Same log, kept in the database so the App Worker can read it. */
|
|
140
|
+
logText: text("log_text").notNull().default(""),
|
|
141
|
+
attempts: integer("attempts").notNull().default(0),
|
|
142
|
+
/** Not claimable before this: how a retry backs off. */
|
|
143
|
+
runAfter: integer("run_after", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
|
|
144
|
+
/**
|
|
145
|
+
* A claim that expires. Whoever holds it renews it while working, so a
|
|
146
|
+
* task whose dispatcher died can be told apart from one still being worked on.
|
|
147
|
+
*/
|
|
148
|
+
leaseUntil: integer("lease_until", { mode: "timestamp_ms" }),
|
|
149
|
+
cancelRequested: integer("cancel_requested", { mode: "boolean" }).notNull().default(false),
|
|
150
|
+
/**
|
|
151
|
+
* Set when the task came from a signal rather than from a pasted link.
|
|
152
|
+
* The signals table already prevents a loop acting twice; this is the
|
|
153
|
+
* second lock, held by the database, in case two polls overlap.
|
|
154
|
+
*/
|
|
155
|
+
dedupeKey: text("dedupe_key"),
|
|
156
|
+
durationMs: integer("duration_ms"),
|
|
157
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
|
|
158
|
+
startedAt: integer("started_at", { mode: "timestamp_ms" }),
|
|
159
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
160
|
+
}, (table) => [
|
|
161
|
+
index("tasks_loop_idx").on(table.loopId),
|
|
162
|
+
index("tasks_created_idx").on(table.createdAt),
|
|
163
|
+
index("tasks_claim_idx").on(table.state, table.runAfter),
|
|
164
|
+
uniqueIndex("tasks_dedupe_idx").on(table.dedupeKey)
|
|
165
|
+
]);
|
|
166
|
+
/**
|
|
167
|
+
* One instance of one of a connector's workflows. The workflow decides what to
|
|
168
|
+
* watch for, what to ask, and where to write; a loop only carries the answers
|
|
169
|
+
* to the few questions the workflow asks. Settings are a JSON blob because
|
|
170
|
+
* only the connector knows what can be narrowed.
|
|
171
|
+
*/
|
|
172
|
+
var loops = sqliteTable("loops", {
|
|
173
|
+
id: text("id").primaryKey(),
|
|
174
|
+
name: text("name").notNull(),
|
|
175
|
+
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
|
176
|
+
/** Lowest first. The first matching loop wins, so order is meaningful. */
|
|
177
|
+
priority: integer("priority").notNull(),
|
|
178
|
+
connectorId: text("connector_id").notNull(),
|
|
179
|
+
workflowId: text("workflow_id").notNull(),
|
|
180
|
+
settings: text("settings", { mode: "json" }).$type().notNull().default({}),
|
|
181
|
+
/**
|
|
182
|
+
* What the agent is asked to do. Copied from the workflow when the loop is
|
|
183
|
+
* created, and the loop's own words from then on.
|
|
184
|
+
*
|
|
185
|
+
* Copied rather than referenced because this is the part a person has
|
|
186
|
+
* opinions about, and an opinion that a later release can overwrite is not
|
|
187
|
+
* worth having. It is only prose, so nothing else has to agree with it.
|
|
188
|
+
*/
|
|
189
|
+
prompt: text("prompt").notNull(),
|
|
190
|
+
/** Added after the prompt, for a note that is not worth editing it over. */
|
|
191
|
+
guidance: text("guidance"),
|
|
192
|
+
/** Null means whichever agent is currently the default. */
|
|
193
|
+
agentId: text("agent_id"),
|
|
194
|
+
/**
|
|
195
|
+
* Which action carries the answer back. Copied from the workflow when the
|
|
196
|
+
* loop is created and the loop's own from then on, so a workflow that
|
|
197
|
+
* changes its mind about where to write does not quietly move somebody's
|
|
198
|
+
* loop with it.
|
|
199
|
+
*
|
|
200
|
+
* The connector is stored apart from the one the loop watches because it
|
|
201
|
+
* need not be the same one. Being asked for a review on GitHub and
|
|
202
|
+
* answering in a chat is two connectors, and this column is what makes it
|
|
203
|
+
* one loop.
|
|
204
|
+
*/
|
|
205
|
+
actionConnectorId: text("action_connector_id").notNull(),
|
|
206
|
+
actionId: text("action_id").notNull(),
|
|
207
|
+
/** Answers to the action's own `target` fields. Empty means: back to the source. */
|
|
208
|
+
actionTarget: text("action_target", { mode: "json" }).$type().notNull().default({}),
|
|
209
|
+
/**
|
|
210
|
+
* Null until the loop has been looked at once. That first look is what
|
|
211
|
+
* separates the backlog that predates the loop from everything after it,
|
|
212
|
+
* so it is worth being able to tell the two apart.
|
|
213
|
+
*/
|
|
214
|
+
polledAt: integer("polled_at", { mode: "timestamp_ms" }),
|
|
215
|
+
/**
|
|
216
|
+
* How long to leave between looks, or null to look every time the dispatcher
|
|
217
|
+
* does.
|
|
218
|
+
*
|
|
219
|
+
* Worth having because looking often and acting cheaply pull in opposite
|
|
220
|
+
* directions. A loop that decides what is worth doing by weighing a batch
|
|
221
|
+
* has nothing to weigh when it is handed one item at a time, so for mail,
|
|
222
|
+
* where nothing turns on the next two minutes, waiting is what makes the
|
|
223
|
+
* weighing possible at all.
|
|
224
|
+
*/
|
|
225
|
+
pollEveryMs: integer("poll_every_ms"),
|
|
226
|
+
/** Why the last look failed, if it did. Cleared by a look that works. */
|
|
227
|
+
pollError: text("poll_error"),
|
|
228
|
+
/** Where a stream got to. The connector's own business. See `poll`. */
|
|
229
|
+
pollCursor: text("poll_cursor", { mode: "json" }).$type(),
|
|
230
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
|
|
231
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
232
|
+
}, (table) => [index("loops_priority_idx").on(table.priority)]);
|
|
233
|
+
/**
|
|
234
|
+
* What a loop has already noticed. A loop acts once per key and never again,
|
|
235
|
+
* which is the whole of not reviewing the same commit twice.
|
|
236
|
+
*/
|
|
237
|
+
var SIGNAL_OUTCOME = [
|
|
238
|
+
"queued",
|
|
239
|
+
"backlog",
|
|
240
|
+
"held",
|
|
241
|
+
"superseded"
|
|
242
|
+
];
|
|
243
|
+
var signals = sqliteTable("signals", {
|
|
244
|
+
loopId: text("loop_id").notNull().references(() => loops.id, { onDelete: "cascade" }),
|
|
245
|
+
/** The connector's own idea of identity: changing it means act again. */
|
|
246
|
+
key: text("key").notNull(),
|
|
247
|
+
outcome: text("outcome").$type().notNull(),
|
|
248
|
+
/** Enough to show and to act on later, without asking the service again. */
|
|
249
|
+
sourceKind: text("source_kind").$type().notNull(),
|
|
250
|
+
sourceRef: text("source_ref").notNull(),
|
|
251
|
+
sourcePayload: text("source_payload", { mode: "json" }).$type(),
|
|
252
|
+
sourceTitle: text("source_title").notNull(),
|
|
253
|
+
sourceUrl: text("source_url").notNull(),
|
|
254
|
+
/** In the words shown to whoever has to decide whether to run it anyway. */
|
|
255
|
+
hold: text("hold"),
|
|
256
|
+
taskId: text("task_id"),
|
|
257
|
+
seenAt: integer("seen_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
258
|
+
}, (table) => [primaryKey({ columns: [table.loopId, table.key] }), index("signals_outcome_idx").on(table.loopId, table.outcome)]);
|
|
259
|
+
/**
|
|
260
|
+
* Agents are discovered on each runner, so nothing about their presence is
|
|
261
|
+
* stored here. Only the choices a person makes live in this table.
|
|
262
|
+
*/
|
|
263
|
+
var agentSettings = sqliteTable("agent_settings", {
|
|
264
|
+
agentId: text("agent_id").primaryKey(),
|
|
265
|
+
permissionMode: text("permission_mode").$type().notNull(),
|
|
266
|
+
model: text("model"),
|
|
267
|
+
timeoutMs: integer("timeout_ms").notNull(),
|
|
268
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
269
|
+
});
|
|
270
|
+
/** Small key/value store for single settings such as the default agent. */
|
|
271
|
+
var appSettings = sqliteTable("app_settings", {
|
|
272
|
+
key: text("key").primaryKey(),
|
|
273
|
+
value: text("value", { mode: "json" }).$type().notNull(),
|
|
274
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
275
|
+
});
|
|
276
|
+
/**
|
|
277
|
+
* A joined runner that can run agents.
|
|
278
|
+
*/
|
|
279
|
+
/**
|
|
280
|
+
* Credentials when App and Dispatcher do not share a keychain. Encrypted with
|
|
281
|
+
* LOOPABLE_MASTER_KEY. Local laptop installs keep using the OS store.
|
|
282
|
+
*/
|
|
283
|
+
var secrets = sqliteTable("secrets", {
|
|
284
|
+
key: text("key").primaryKey(),
|
|
285
|
+
value: text("value").notNull(),
|
|
286
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
287
|
+
});
|
|
288
|
+
var runners = sqliteTable("runners", {
|
|
289
|
+
id: text("id").primaryKey(),
|
|
290
|
+
name: text("name").notNull(),
|
|
291
|
+
hostname: text("hostname").notNull(),
|
|
292
|
+
status: text("status").$type().notNull().default("offline"),
|
|
293
|
+
inventory: text("inventory", { mode: "json" }).$type().notNull().default([]),
|
|
294
|
+
lastSeenAt: integer("last_seen_at", { mode: "timestamp_ms" }),
|
|
295
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
296
|
+
});
|
|
297
|
+
function databaseUrl() {
|
|
298
|
+
if (process.env.LOOPABLE_DATABASE_URL) return process.env.LOOPABLE_DATABASE_URL;
|
|
299
|
+
return pathToFileURL(dbPath()).href;
|
|
300
|
+
}
|
|
301
|
+
function create() {
|
|
302
|
+
const client = createClient({
|
|
303
|
+
url: databaseUrl(),
|
|
304
|
+
authToken: process.env.LOOPABLE_DATABASE_AUTH_TOKEN
|
|
305
|
+
});
|
|
306
|
+
return drizzle(client, { schema: schema_exports });
|
|
307
|
+
}
|
|
308
|
+
function migrationsFolder() {
|
|
309
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
310
|
+
return [
|
|
311
|
+
join(here, "../drizzle"),
|
|
312
|
+
join(here, "../../../drizzle"),
|
|
313
|
+
join(process.cwd(), "drizzle")
|
|
314
|
+
].find((folder) => existsSync(folder)) ?? join(process.cwd(), "drizzle");
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The dispatcher applies migrations. The App Worker has no drizzle folder on disk
|
|
318
|
+
* in production, and must not race the dispatcher on a fresh database.
|
|
319
|
+
*/
|
|
320
|
+
async function migrateIfNeeded() {
|
|
321
|
+
if (process.env.LOOPABLE_SKIP_MIGRATE === "1") return;
|
|
322
|
+
const folder = migrationsFolder();
|
|
323
|
+
if (!existsSync(folder)) return;
|
|
324
|
+
await migrate(db(), { migrationsFolder: folder });
|
|
325
|
+
}
|
|
326
|
+
function rowsChanged(result) {
|
|
327
|
+
return Number(result.rowsAffected ?? result.changes ?? 0);
|
|
328
|
+
}
|
|
329
|
+
var cache = globalThis;
|
|
330
|
+
function db() {
|
|
331
|
+
cache.__loopableDb ??= create();
|
|
332
|
+
return cache.__loopableDb;
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
export { connections as a, loops as c, runDir as d, runners as f, tasks as g, signals as h, authAttempts as i, migrateIfNeeded as l, secrets as m, agentSettings as n, dataDir as o, secretNamespace as p, appSettings as r, db as s, __exportAll as t, rowsChanged as u };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { U as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
|
|
2
|
+
import { I as Badge } from "./router-3WmpE5b_.mjs";
|
|
3
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/connection-status-Bi5peUAk.js
|
|
4
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
5
|
+
var LABELS = {
|
|
6
|
+
connected: {
|
|
7
|
+
text: "Connected",
|
|
8
|
+
variant: "default"
|
|
9
|
+
},
|
|
10
|
+
needs_reauth: {
|
|
11
|
+
text: "Needs reconnecting",
|
|
12
|
+
variant: "destructive"
|
|
13
|
+
},
|
|
14
|
+
error: {
|
|
15
|
+
text: "Error",
|
|
16
|
+
variant: "destructive"
|
|
17
|
+
},
|
|
18
|
+
paused: {
|
|
19
|
+
text: "Paused",
|
|
20
|
+
variant: "secondary"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function ConnectionStatusBadge({ status }) {
|
|
24
|
+
const label = LABELS[status] ?? LABELS.error;
|
|
25
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
26
|
+
variant: label.variant,
|
|
27
|
+
children: label.text
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ConnectionStatusBadge as t };
|