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,311 @@
|
|
|
1
|
+
import { n as createServerFn } from "./ssr.mjs";
|
|
2
|
+
import { d as and, f as eq, l as asc, p as inArray, v as sql } from "../_libs/drizzle-orm.mjs";
|
|
3
|
+
import { a as connections, c as loops, h as signals, s as db } from "./client-CKkOfh-s.mjs";
|
|
4
|
+
import { a as connectorManifest, i as connectorAction, s as connectorWorkflow } from "./connections-qnw2GVlP.mjs";
|
|
5
|
+
import { n as agentManifest, r as listAgents } from "./agents-Btql102J.mjs";
|
|
6
|
+
import { t as availableAgentIds } from "./runners-DtXs4Q8a.mjs";
|
|
7
|
+
import { n as enqueueSignal } from "./tasks-vcoodp_D.mjs";
|
|
8
|
+
import { t as createServerRpc } from "./createServerRpc-DPDrnFdH.mjs";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/loops-APFcP0y6.js
|
|
11
|
+
var NAME_LIMIT = 80;
|
|
12
|
+
var PROMPT_LIMIT = 8e3;
|
|
13
|
+
var GUIDANCE_LIMIT = 4e3;
|
|
14
|
+
function toView(row) {
|
|
15
|
+
return {
|
|
16
|
+
id: row.id,
|
|
17
|
+
name: row.name,
|
|
18
|
+
enabled: row.enabled,
|
|
19
|
+
priority: row.priority,
|
|
20
|
+
connectorId: row.connectorId,
|
|
21
|
+
workflowId: row.workflowId,
|
|
22
|
+
settings: row.settings,
|
|
23
|
+
prompt: row.prompt,
|
|
24
|
+
guidance: row.guidance,
|
|
25
|
+
agentId: row.agentId,
|
|
26
|
+
actionConnectorId: row.actionConnectorId,
|
|
27
|
+
actionId: row.actionId,
|
|
28
|
+
actionTarget: row.actionTarget,
|
|
29
|
+
pollEveryMs: row.pollEveryMs,
|
|
30
|
+
createdAt: row.createdAt.toISOString(),
|
|
31
|
+
updatedAt: row.updatedAt.toISOString()
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Only the fields the chosen workflow declares survive, and each is coerced to
|
|
36
|
+
* the kind it was declared as. A stored setting the connector no longer offers
|
|
37
|
+
* would otherwise sit there narrowing things invisibly.
|
|
38
|
+
*/
|
|
39
|
+
function cleanConditions(fields, input) {
|
|
40
|
+
const cleaned = {};
|
|
41
|
+
for (const field of fields) {
|
|
42
|
+
const value = input[field.key];
|
|
43
|
+
switch (field.kind) {
|
|
44
|
+
case "boolean":
|
|
45
|
+
cleaned[field.key] = typeof value === "boolean" ? value : field.default;
|
|
46
|
+
break;
|
|
47
|
+
case "string_list":
|
|
48
|
+
cleaned[field.key] = Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim() !== "") : [];
|
|
49
|
+
break;
|
|
50
|
+
case "select":
|
|
51
|
+
cleaned[field.key] = typeof value === "string" && field.options.some((option) => option.value === value) ? value : field.default;
|
|
52
|
+
break;
|
|
53
|
+
default: cleaned[field.key] = typeof value === "string" ? value.trim() : "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return cleaned;
|
|
57
|
+
}
|
|
58
|
+
function validate(draft) {
|
|
59
|
+
const manifest = connectorManifest(draft.connectorId);
|
|
60
|
+
if (!manifest) throw new Error(`Unknown connector: ${draft.connectorId}`);
|
|
61
|
+
const workflow = connectorWorkflow(draft.connectorId, draft.workflowId);
|
|
62
|
+
if (!workflow) throw new Error(`${manifest.name} does not offer ${draft.workflowId}.`);
|
|
63
|
+
const writer = connectorManifest(draft.actionConnectorId);
|
|
64
|
+
if (!writer) throw new Error(`Unknown connector: ${draft.actionConnectorId}`);
|
|
65
|
+
const action = connectorAction(draft.actionConnectorId, draft.actionId);
|
|
66
|
+
if (!action) throw new Error(`${writer.name} cannot ${draft.actionId}.`);
|
|
67
|
+
if (draft.agentId && !agentManifest(draft.agentId)) throw new Error(`Unknown agent: ${draft.agentId}`);
|
|
68
|
+
const name = draft.name.trim();
|
|
69
|
+
if (!name) throw new Error("Give the loop a name.");
|
|
70
|
+
if (name.length > NAME_LIMIT) throw new Error(`Keep the name under ${NAME_LIMIT} characters.`);
|
|
71
|
+
const prompt = draft.prompt.trim();
|
|
72
|
+
if (!prompt) throw new Error("Say what the agent should do.");
|
|
73
|
+
if (prompt.length > PROMPT_LIMIT) throw new Error(`Keep what you ask under ${PROMPT_LIMIT} characters.`);
|
|
74
|
+
const guidance = draft.guidance?.trim() || null;
|
|
75
|
+
if (guidance && guidance.length > GUIDANCE_LIMIT) throw new Error(`Keep the guidance under ${GUIDANCE_LIMIT} characters.`);
|
|
76
|
+
if (workflow.runsIn === "folder") {
|
|
77
|
+
const folder = draft.settings.folder;
|
|
78
|
+
if (typeof folder !== "string" || folder.trim() === "") throw new Error("Say which folder this should work in.");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...draft,
|
|
82
|
+
name,
|
|
83
|
+
prompt,
|
|
84
|
+
guidance,
|
|
85
|
+
settings: cleanConditions(workflow.settings, draft.settings),
|
|
86
|
+
actionTarget: cleanConditions(action.target, draft.actionTarget)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function listLoops() {
|
|
90
|
+
return (await db().select().from(loops).orderBy(asc(loops.priority)).all()).map(toView);
|
|
91
|
+
}
|
|
92
|
+
async function getLoop(id) {
|
|
93
|
+
const row = await db().select().from(loops).where(eq(loops.id, id)).get();
|
|
94
|
+
return row ? toView(row) : null;
|
|
95
|
+
}
|
|
96
|
+
async function createLoop(draft) {
|
|
97
|
+
const checked = validate(draft);
|
|
98
|
+
const last = await db().select({ value: sql`max(${loops.priority})` }).from(loops).get();
|
|
99
|
+
const id = randomUUID();
|
|
100
|
+
await db().insert(loops).values({
|
|
101
|
+
id,
|
|
102
|
+
name: checked.name,
|
|
103
|
+
enabled: checked.enabled,
|
|
104
|
+
priority: (last?.value ?? 0) + 1,
|
|
105
|
+
connectorId: checked.connectorId,
|
|
106
|
+
workflowId: checked.workflowId,
|
|
107
|
+
settings: checked.settings,
|
|
108
|
+
prompt: checked.prompt,
|
|
109
|
+
guidance: checked.guidance,
|
|
110
|
+
agentId: checked.agentId,
|
|
111
|
+
actionConnectorId: checked.actionConnectorId,
|
|
112
|
+
actionId: checked.actionId,
|
|
113
|
+
actionTarget: checked.actionTarget,
|
|
114
|
+
pollEveryMs: checked.pollEveryMs
|
|
115
|
+
}).run();
|
|
116
|
+
return await getLoop(id);
|
|
117
|
+
}
|
|
118
|
+
async function updateLoop(id, draft) {
|
|
119
|
+
if (!await getLoop(id)) throw new Error("Loop not found");
|
|
120
|
+
const checked = validate(draft);
|
|
121
|
+
await db().update(loops).set({
|
|
122
|
+
name: checked.name,
|
|
123
|
+
enabled: checked.enabled,
|
|
124
|
+
connectorId: checked.connectorId,
|
|
125
|
+
workflowId: checked.workflowId,
|
|
126
|
+
settings: checked.settings,
|
|
127
|
+
prompt: checked.prompt,
|
|
128
|
+
guidance: checked.guidance,
|
|
129
|
+
agentId: checked.agentId,
|
|
130
|
+
actionConnectorId: checked.actionConnectorId,
|
|
131
|
+
actionId: checked.actionId,
|
|
132
|
+
actionTarget: checked.actionTarget,
|
|
133
|
+
pollEveryMs: checked.pollEveryMs,
|
|
134
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
135
|
+
}).where(eq(loops.id, id)).run();
|
|
136
|
+
return await getLoop(id);
|
|
137
|
+
}
|
|
138
|
+
async function setLoopEnabled(id, enabled) {
|
|
139
|
+
if (!await getLoop(id)) throw new Error("Loop not found");
|
|
140
|
+
await db().update(loops).set({
|
|
141
|
+
enabled,
|
|
142
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
143
|
+
}).where(eq(loops.id, id)).run();
|
|
144
|
+
return await getLoop(id);
|
|
145
|
+
}
|
|
146
|
+
async function deleteLoop(id) {
|
|
147
|
+
await db().delete(loops).where(eq(loops.id, id)).run();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The first matching loop wins, so a person has to be able to say which comes
|
|
151
|
+
* first. Swapping with the neighbour keeps that to one obvious gesture.
|
|
152
|
+
*/
|
|
153
|
+
async function moveLoop(id, direction) {
|
|
154
|
+
const ordered = await db().select().from(loops).orderBy(asc(loops.priority)).all();
|
|
155
|
+
const index = ordered.findIndex((row) => row.id === id);
|
|
156
|
+
if (index === -1) throw new Error("Loop not found");
|
|
157
|
+
const target = direction === "up" ? index - 1 : index + 1;
|
|
158
|
+
if (target < 0 || target >= ordered.length) return ordered.map(toView);
|
|
159
|
+
const moving = ordered[index];
|
|
160
|
+
const neighbour = ordered[target];
|
|
161
|
+
await db().transaction(async (tx) => {
|
|
162
|
+
await tx.update(loops).set({ priority: neighbour.priority }).where(eq(loops.id, moving.id)).run();
|
|
163
|
+
await tx.update(loops).set({ priority: moving.priority }).where(eq(loops.id, neighbour.id)).run();
|
|
164
|
+
});
|
|
165
|
+
return await listLoops();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A loop can be written before anything is connected or installed, so the page
|
|
169
|
+
* says what is still missing rather than refusing to save.
|
|
170
|
+
*/
|
|
171
|
+
async function loopReadiness() {
|
|
172
|
+
const agents = await listAgents();
|
|
173
|
+
const connected = await db().selectDistinct({ connectorId: connections.connectorId }).from(connections).all();
|
|
174
|
+
const available = await availableAgentIds();
|
|
175
|
+
const host = await availableAgentIds(true);
|
|
176
|
+
const chosen = agents.find((agent) => agent.isDefault)?.agentId ?? null;
|
|
177
|
+
const defaultAgentId = (chosen && available.includes(chosen) ? chosen : null) ?? (available.length === 1 ? available[0] : null);
|
|
178
|
+
return {
|
|
179
|
+
connectedConnectorIds: connected.map((row) => row.connectorId),
|
|
180
|
+
defaultAgentId,
|
|
181
|
+
availableAgentIds: available,
|
|
182
|
+
hostAgentIds: host
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function toBacklogItem(row) {
|
|
186
|
+
return {
|
|
187
|
+
key: row.key,
|
|
188
|
+
sourceKind: row.sourceKind,
|
|
189
|
+
sourceRef: row.sourceRef,
|
|
190
|
+
sourceTitle: row.sourceTitle,
|
|
191
|
+
sourceUrl: row.sourceUrl,
|
|
192
|
+
hold: row.hold,
|
|
193
|
+
seenAt: row.seenAt.toISOString()
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Everything a loop has noticed and never acted on, whether it was already
|
|
198
|
+
* waiting when the loop was made or the connector held it back. They are one
|
|
199
|
+
* list because they are one question: this was asked of you and nothing has
|
|
200
|
+
* happened, do you want it run?
|
|
201
|
+
*/
|
|
202
|
+
var WAITING = ["backlog", "held"];
|
|
203
|
+
/** What the loop page needs to say whether the loop is actually watching. */
|
|
204
|
+
async function loopPollState(loopId) {
|
|
205
|
+
const loop = await db().select().from(loops).where(eq(loops.id, loopId)).get();
|
|
206
|
+
const backlog = await db().select().from(signals).where(and(eq(signals.loopId, loopId), inArray(signals.outcome, [...WAITING]))).orderBy(asc(signals.sourceRef)).all();
|
|
207
|
+
return {
|
|
208
|
+
polledAt: loop?.polledAt?.toISOString() ?? null,
|
|
209
|
+
pollError: loop?.pollError ?? null,
|
|
210
|
+
backlog: backlog.map(toBacklogItem)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Run everything the loop is holding. Asked for explicitly, because it is the
|
|
215
|
+
* one moment a loop does a month of work at once, and because something held
|
|
216
|
+
* back for being too big is being run against the connector's advice.
|
|
217
|
+
*/
|
|
218
|
+
async function runBacklog(loopId) {
|
|
219
|
+
const loop = await db().select().from(loops).where(eq(loops.id, loopId)).get();
|
|
220
|
+
if (!loop) throw new Error("Loop not found");
|
|
221
|
+
const waiting = await db().select().from(signals).where(and(eq(signals.loopId, loopId), inArray(signals.outcome, [...WAITING]))).all();
|
|
222
|
+
for (const row of waiting) {
|
|
223
|
+
const task = await enqueueSignal({
|
|
224
|
+
loop,
|
|
225
|
+
signal: {
|
|
226
|
+
key: row.key,
|
|
227
|
+
kind: row.sourceKind,
|
|
228
|
+
ref: row.sourceRef,
|
|
229
|
+
title: row.sourceTitle,
|
|
230
|
+
url: row.sourceUrl,
|
|
231
|
+
payload: row.sourcePayload ?? void 0
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
await db().update(signals).set({
|
|
235
|
+
outcome: "queued",
|
|
236
|
+
taskId: task.id
|
|
237
|
+
}).where(and(eq(signals.loopId, loopId), eq(signals.key, row.key))).run();
|
|
238
|
+
}
|
|
239
|
+
return waiting.length;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Saving is both making and changing, told apart by whether there is an id.
|
|
243
|
+
* A loop chosen and not yet saved has none, and nothing on the server knew
|
|
244
|
+
* about it until now.
|
|
245
|
+
*/
|
|
246
|
+
var edit = (data) => data;
|
|
247
|
+
var getLoopsPage_createServerFn_handler = createServerRpc({
|
|
248
|
+
id: "6d5a90e9ec4ebf70d8e2ac4aff960533a5e68d21b289033cc7e0751d00554c93",
|
|
249
|
+
name: "getLoopsPage",
|
|
250
|
+
filename: "src/server/functions/loops.ts"
|
|
251
|
+
}, (opts) => getLoopsPage.__executeServer(opts));
|
|
252
|
+
var getLoopsPage = createServerFn({ method: "GET" }).handler(getLoopsPage_createServerFn_handler, async () => ({
|
|
253
|
+
loops: await listLoops(),
|
|
254
|
+
readiness: await loopReadiness()
|
|
255
|
+
}));
|
|
256
|
+
var getLoopReadiness_createServerFn_handler = createServerRpc({
|
|
257
|
+
id: "997d2dd097598c44811fe97def167b79d33e4349c51e783058da648e860c7c1f",
|
|
258
|
+
name: "getLoopReadiness",
|
|
259
|
+
filename: "src/server/functions/loops.ts"
|
|
260
|
+
}, (opts) => getLoopReadiness.__executeServer(opts));
|
|
261
|
+
var getLoopReadiness = createServerFn({ method: "GET" }).handler(getLoopReadiness_createServerFn_handler, async () => await loopReadiness());
|
|
262
|
+
var getLoopById_createServerFn_handler = createServerRpc({
|
|
263
|
+
id: "81a1af559b2a373901872aa9f9a6546f2f3bda8157d81c21a36ba0d1d337613b",
|
|
264
|
+
name: "getLoopById",
|
|
265
|
+
filename: "src/server/functions/loops.ts"
|
|
266
|
+
}, (opts) => getLoopById.__executeServer(opts));
|
|
267
|
+
var getLoopById = createServerFn({ method: "GET" }).inputValidator((data) => data).handler(getLoopById_createServerFn_handler, async ({ data }) => await getLoop(data.id));
|
|
268
|
+
var saveLoop_createServerFn_handler = createServerRpc({
|
|
269
|
+
id: "e91fc2fd51633c7ca9184fea4beb66fe775ed0b4e6323111a4690da916a78b00",
|
|
270
|
+
name: "saveLoop",
|
|
271
|
+
filename: "src/server/functions/loops.ts"
|
|
272
|
+
}, (opts) => saveLoop.__executeServer(opts));
|
|
273
|
+
var saveLoop = createServerFn({ method: "POST" }).inputValidator(edit).handler(saveLoop_createServerFn_handler, async ({ data }) => {
|
|
274
|
+
const { id, ...values } = data;
|
|
275
|
+
return id ? await updateLoop(id, values) : await createLoop(values);
|
|
276
|
+
});
|
|
277
|
+
var toggleLoop_createServerFn_handler = createServerRpc({
|
|
278
|
+
id: "3fc8a7de98be979f9820a4f5a0ca2e234ed8a7df8e692abb8c4437765474eca0",
|
|
279
|
+
name: "toggleLoop",
|
|
280
|
+
filename: "src/server/functions/loops.ts"
|
|
281
|
+
}, (opts) => toggleLoop.__executeServer(opts));
|
|
282
|
+
var toggleLoop = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(toggleLoop_createServerFn_handler, async ({ data }) => await setLoopEnabled(data.id, data.enabled));
|
|
283
|
+
var removeLoop_createServerFn_handler = createServerRpc({
|
|
284
|
+
id: "b5a89ca2d07f4ccf2827b5132ca52e7c7a42d584e702b0ab3963b2d5763c35b0",
|
|
285
|
+
name: "removeLoop",
|
|
286
|
+
filename: "src/server/functions/loops.ts"
|
|
287
|
+
}, (opts) => removeLoop.__executeServer(opts));
|
|
288
|
+
var removeLoop = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(removeLoop_createServerFn_handler, async ({ data }) => {
|
|
289
|
+
await deleteLoop(data.id);
|
|
290
|
+
return await listLoops();
|
|
291
|
+
});
|
|
292
|
+
var reorderLoop_createServerFn_handler = createServerRpc({
|
|
293
|
+
id: "7d7d5f80a4f0d2b3bb659d7dc2a91ea5983880648b9d1aa24356e7340505930f",
|
|
294
|
+
name: "reorderLoop",
|
|
295
|
+
filename: "src/server/functions/loops.ts"
|
|
296
|
+
}, (opts) => reorderLoop.__executeServer(opts));
|
|
297
|
+
var reorderLoop = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(reorderLoop_createServerFn_handler, async ({ data }) => await moveLoop(data.id, data.direction));
|
|
298
|
+
var getLoopPollState_createServerFn_handler = createServerRpc({
|
|
299
|
+
id: "4fd3855e3e9ed954e102071a55b98d91651c9b1e353161f7223768ed6f2b8c28",
|
|
300
|
+
name: "getLoopPollState",
|
|
301
|
+
filename: "src/server/functions/loops.ts"
|
|
302
|
+
}, (opts) => getLoopPollState.__executeServer(opts));
|
|
303
|
+
var getLoopPollState = createServerFn({ method: "GET" }).inputValidator((data) => data).handler(getLoopPollState_createServerFn_handler, async ({ data }) => await loopPollState(data.id));
|
|
304
|
+
var runLoopBacklog_createServerFn_handler = createServerRpc({
|
|
305
|
+
id: "65494cbfd06143dbd9a012808fbba21163d9902e387869503fb8b68b0674902b",
|
|
306
|
+
name: "runLoopBacklog",
|
|
307
|
+
filename: "src/server/functions/loops.ts"
|
|
308
|
+
}, (opts) => runLoopBacklog.__executeServer(opts));
|
|
309
|
+
var runLoopBacklog = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(runLoopBacklog_createServerFn_handler, async ({ data }) => ({ queued: await runBacklog(data.id) }));
|
|
310
|
+
//#endregion
|
|
311
|
+
export { getLoopById_createServerFn_handler, getLoopPollState_createServerFn_handler, getLoopReadiness_createServerFn_handler, getLoopsPage_createServerFn_handler, removeLoop_createServerFn_handler, reorderLoop_createServerFn_handler, runLoopBacklog_createServerFn_handler, saveLoop_createServerFn_handler, toggleLoop_createServerFn_handler };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { i as __toESM } from "../_runtime.mjs";
|
|
2
|
+
import { U as require_jsx_runtime, W as require_react } from "../_libs/@base-ui/react+[...].mjs";
|
|
3
|
+
import { f as Link, m as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
4
|
+
import { a as connectorManifest, i as connectorAction, s as connectorWorkflow } from "./connections-qnw2GVlP.mjs";
|
|
5
|
+
import { n as agentManifest } from "./agents-Btql102J.mjs";
|
|
6
|
+
import { M as CircleAlert, R as ArrowUp, V as ArrowDown, i as Trash2, u as Plus } from "../_libs/lucide-react.mjs";
|
|
7
|
+
import { n as toast } from "../_libs/sonner.mjs";
|
|
8
|
+
import { C as Button$1, I as Badge, L as Card, O as toggleLoop, R as CardContent, T as reorderLoop, f as useLiveRefresh, s as Route$11, w as removeLoop, z as CardHeader } from "./router-3WmpE5b_.mjs";
|
|
9
|
+
import { t as Switch$1 } from "./switch-ReNglgH6.mjs";
|
|
10
|
+
import { t as ConnectorIcon } from "./connector-icon-DZXQLLFo.mjs";
|
|
11
|
+
import { n as AlertDescription, r as AlertTitle, t as Alert } from "./alert-NEiOCTOl.mjs";
|
|
12
|
+
import { n as issuesFor, t as gapsFor } from "./gaps-RAFSXLZf.mjs";
|
|
13
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/loops-xt2Bn7_q.js
|
|
14
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
15
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
16
|
+
function LoopsPage() {
|
|
17
|
+
const { loops, readiness } = Route$11.useLoaderData();
|
|
18
|
+
useLiveRefresh();
|
|
19
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
20
|
+
className: "flex flex-col gap-6",
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
23
|
+
className: "flex items-start gap-4",
|
|
24
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25
|
+
className: "flex-1",
|
|
26
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
27
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
28
|
+
children: "Loops"
|
|
29
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
30
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
31
|
+
children: "Each loop runs one workflow on its own and writes the result back. The first loop that matches something is the one that runs, so order matters."
|
|
32
|
+
})]
|
|
33
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
|
|
34
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
|
|
35
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "New loop"]
|
|
36
|
+
})]
|
|
37
|
+
}),
|
|
38
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Gaps, {
|
|
39
|
+
readiness,
|
|
40
|
+
loops
|
|
41
|
+
}),
|
|
42
|
+
loops.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
|
|
43
|
+
className: "flex flex-col items-center gap-3 py-10 text-center",
|
|
44
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
45
|
+
className: "text-sm text-muted-foreground",
|
|
46
|
+
children: "No loops yet, so nothing is being watched for."
|
|
47
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
|
|
48
|
+
variant: "outline",
|
|
49
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
|
|
50
|
+
children: "See what Loopable can do"
|
|
51
|
+
})]
|
|
52
|
+
}) }) : null,
|
|
53
|
+
loops.map((loop, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopCard, {
|
|
54
|
+
loop,
|
|
55
|
+
readiness,
|
|
56
|
+
position: index + 1,
|
|
57
|
+
first: index === 0,
|
|
58
|
+
last: index === loops.length - 1
|
|
59
|
+
}, loop.id))
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Says what a loop still needs, without standing in the way of writing one. */
|
|
64
|
+
function Gaps({ readiness, loops }) {
|
|
65
|
+
const missing = gapsFor(readiness, loops);
|
|
66
|
+
if (missing.length === 0) return null;
|
|
67
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Loops can be written, but nothing will run yet" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDescription, {
|
|
68
|
+
className: "flex flex-col gap-1",
|
|
69
|
+
children: [missing.map((line) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: line }, line)), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
70
|
+
className: "mt-1 flex gap-3 text-xs",
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
73
|
+
to: "/connectors",
|
|
74
|
+
className: "underline",
|
|
75
|
+
children: "Connectors"
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
78
|
+
to: "/agents",
|
|
79
|
+
className: "underline",
|
|
80
|
+
children: "Agents"
|
|
81
|
+
}),
|
|
82
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
83
|
+
to: "/runners",
|
|
84
|
+
className: "underline",
|
|
85
|
+
children: "Runners"
|
|
86
|
+
})
|
|
87
|
+
]
|
|
88
|
+
})]
|
|
89
|
+
})] });
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Where this loop writes, in its own words rather than the workflow's. A loop
|
|
93
|
+
* may have been pointed somewhere else since it was made, and the card is the
|
|
94
|
+
* one place someone scanning a list would notice.
|
|
95
|
+
*/
|
|
96
|
+
function writesOf(loop) {
|
|
97
|
+
const action = connectorAction(loop.actionConnectorId, loop.actionId);
|
|
98
|
+
if (!action) return `it writes with ${loop.actionId}, which is no longer offered`;
|
|
99
|
+
const writer = connectorManifest(loop.actionConnectorId);
|
|
100
|
+
const where = loop.actionConnectorId === loop.connectorId ? "" : ` on ${writer?.name ?? loop.actionConnectorId}`;
|
|
101
|
+
return `it will ${action.name.toLowerCase()}${where}`;
|
|
102
|
+
}
|
|
103
|
+
function LoopCard({ loop, readiness, position, first, last }) {
|
|
104
|
+
const router = useRouter();
|
|
105
|
+
const manifest = connectorManifest(loop.connectorId);
|
|
106
|
+
const workflow = connectorWorkflow(loop.connectorId, loop.workflowId);
|
|
107
|
+
const agent = loop.agentId ? agentManifest(loop.agentId) : void 0;
|
|
108
|
+
const issues = issuesFor(readiness, loop);
|
|
109
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
110
|
+
const run = async (work, message) => {
|
|
111
|
+
setBusy(true);
|
|
112
|
+
try {
|
|
113
|
+
await work();
|
|
114
|
+
await router.invalidate();
|
|
115
|
+
if (message) toast.success(message);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
toast.error(error instanceof Error ? error.message : String(error));
|
|
118
|
+
} finally {
|
|
119
|
+
setBusy(false);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, {
|
|
123
|
+
className: loop.enabled ? void 0 : "opacity-70",
|
|
124
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
|
|
125
|
+
className: "flex flex-row items-start gap-3",
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
128
|
+
className: "mt-0.5 w-5 text-sm text-muted-foreground",
|
|
129
|
+
children: position
|
|
130
|
+
}),
|
|
131
|
+
manifest ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
|
|
132
|
+
icon: manifest.icon,
|
|
133
|
+
accent: manifest.accent
|
|
134
|
+
}) : null,
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
136
|
+
className: "flex-1",
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
139
|
+
className: "flex flex-wrap items-center gap-2",
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
142
|
+
to: "/loops/$loopId",
|
|
143
|
+
params: { loopId: loop.id },
|
|
144
|
+
className: "font-medium hover:underline",
|
|
145
|
+
children: loop.name
|
|
146
|
+
}),
|
|
147
|
+
loop.enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
148
|
+
variant: "secondary",
|
|
149
|
+
children: "Off"
|
|
150
|
+
}),
|
|
151
|
+
issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
152
|
+
variant: "outline",
|
|
153
|
+
children: "Cannot run"
|
|
154
|
+
}) : null
|
|
155
|
+
]
|
|
156
|
+
}),
|
|
157
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
158
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
159
|
+
children: workflow ? `When ${workflow.trigger}, ${writesOf(loop)}${agent ? `, using ${agent.name}` : ""}.` : `Built on ${loop.workflowId}, which is no longer offered.`
|
|
160
|
+
}),
|
|
161
|
+
issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
162
|
+
className: "mt-2 flex items-start gap-1.5 text-xs text-amber-700 dark:text-amber-500",
|
|
163
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleAlert, { className: "mt-0.5 size-3.5 shrink-0" }), issues[0]]
|
|
164
|
+
}) : null
|
|
165
|
+
]
|
|
166
|
+
}),
|
|
167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
168
|
+
className: "flex items-center gap-1",
|
|
169
|
+
children: [
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
|
|
171
|
+
variant: "ghost",
|
|
172
|
+
size: "icon",
|
|
173
|
+
"aria-label": "Move up",
|
|
174
|
+
disabled: busy || first,
|
|
175
|
+
onClick: () => run(() => reorderLoop({ data: {
|
|
176
|
+
id: loop.id,
|
|
177
|
+
direction: "up"
|
|
178
|
+
} })),
|
|
179
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowUp, {})
|
|
180
|
+
}),
|
|
181
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
|
|
182
|
+
variant: "ghost",
|
|
183
|
+
size: "icon",
|
|
184
|
+
"aria-label": "Move down",
|
|
185
|
+
disabled: busy || last,
|
|
186
|
+
onClick: () => run(() => reorderLoop({ data: {
|
|
187
|
+
id: loop.id,
|
|
188
|
+
direction: "down"
|
|
189
|
+
} })),
|
|
190
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowDown, {})
|
|
191
|
+
}),
|
|
192
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
|
|
193
|
+
checked: loop.enabled,
|
|
194
|
+
"aria-label": "Enabled",
|
|
195
|
+
disabled: busy,
|
|
196
|
+
onCheckedChange: (enabled) => run(() => toggleLoop({ data: {
|
|
197
|
+
id: loop.id,
|
|
198
|
+
enabled
|
|
199
|
+
} }), enabled ? `${loop.name} is on` : `${loop.name} is off`)
|
|
200
|
+
}),
|
|
201
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
|
|
202
|
+
variant: "ghost",
|
|
203
|
+
size: "icon",
|
|
204
|
+
"aria-label": "Delete",
|
|
205
|
+
disabled: busy,
|
|
206
|
+
onClick: () => run(() => removeLoop({ data: { id: loop.id } }), `Deleted ${loop.name}`),
|
|
207
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, {})
|
|
208
|
+
})
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
}), loop.guidance ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
|
|
213
|
+
className: "border-t pt-4",
|
|
214
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
215
|
+
className: "line-clamp-2 text-sm text-muted-foreground",
|
|
216
|
+
children: loop.guidance
|
|
217
|
+
})
|
|
218
|
+
}) : null]
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { LoopsPage as component };
|