@hasna/loops 0.3.37 → 0.3.39
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/README.md +95 -14
- package/dist/cli/index.js +1489 -179
- package/dist/daemon/index.js +494 -43
- package/dist/index.d.ts +1 -1
- package/dist/index.js +818 -53
- package/dist/lib/format.d.ts +3 -1
- package/dist/lib/run-artifacts.d.ts +15 -0
- package/dist/lib/store.d.ts +33 -1
- package/dist/lib/store.js +419 -8
- package/dist/lib/templates.d.ts +10 -0
- package/dist/sdk/index.d.ts +2 -1
- package/dist/sdk/index.js +524 -33
- package/dist/types.d.ts +109 -0
- package/docs/USAGE.md +71 -30
- package/package.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,26 @@ export type ScheduleSpec = OnceSchedule | IntervalSchedule | CronSchedule | Dyna
|
|
|
43
43
|
export interface RuntimePreflightPolicy {
|
|
44
44
|
beforeRun?: boolean;
|
|
45
45
|
}
|
|
46
|
+
export interface OpenAutomationsRuntimeBinding {
|
|
47
|
+
integration: "open-automations";
|
|
48
|
+
role: "runtime";
|
|
49
|
+
handoff: "claim-queue";
|
|
50
|
+
queueOwner: "open-automations";
|
|
51
|
+
runtimeOwner: "open-loops";
|
|
52
|
+
statusCommand: "automations status";
|
|
53
|
+
claimCommand: "automations queue claim";
|
|
54
|
+
completeCommand: "automations queue complete";
|
|
55
|
+
failCommand: "automations queue fail";
|
|
56
|
+
eventHandoff: {
|
|
57
|
+
envelopeCommand: "automations webhooks event";
|
|
58
|
+
handlerCommand: "loops events handle generic";
|
|
59
|
+
pipeExample: string;
|
|
60
|
+
boundary: string;
|
|
61
|
+
};
|
|
62
|
+
requiredEnvironment: string[];
|
|
63
|
+
guarantees: string[];
|
|
64
|
+
nonGoals: string[];
|
|
65
|
+
}
|
|
46
66
|
export interface CommandTarget {
|
|
47
67
|
type: "command";
|
|
48
68
|
command: string;
|
|
@@ -51,6 +71,7 @@ export interface CommandTarget {
|
|
|
51
71
|
shell?: boolean;
|
|
52
72
|
env?: Record<string, string>;
|
|
53
73
|
timeoutMs?: number;
|
|
74
|
+
idleTimeoutMs?: number;
|
|
54
75
|
account?: AccountRef;
|
|
55
76
|
preflight?: RuntimePreflightPolicy;
|
|
56
77
|
}
|
|
@@ -94,6 +115,7 @@ export interface AgentTarget {
|
|
|
94
115
|
authProfile?: string;
|
|
95
116
|
extraArgs?: string[];
|
|
96
117
|
timeoutMs?: number;
|
|
118
|
+
idleTimeoutMs?: number;
|
|
97
119
|
configIsolation?: AgentConfigIsolation;
|
|
98
120
|
permissionMode?: AgentPermissionMode;
|
|
99
121
|
sandbox?: AgentSandbox;
|
|
@@ -115,6 +137,90 @@ export type LoopTarget = ExecutableTarget | WorkflowTarget;
|
|
|
115
137
|
export type WorkflowStatus = "active" | "archived";
|
|
116
138
|
export type WorkflowRunStatus = "running" | "succeeded" | "failed" | "timed_out" | "cancelled";
|
|
117
139
|
export type WorkflowStepRunStatus = "pending" | "running" | "succeeded" | "failed" | "timed_out" | "skipped" | "cancelled";
|
|
140
|
+
export type WorkflowInvocationSourceKind = "task" | "event" | "schedule" | "manual" | "pr" | "review" | "knowledge";
|
|
141
|
+
export type WorkflowInvocationSubjectKind = "repo" | "pr" | "task" | "doc" | "run" | "metric";
|
|
142
|
+
export type WorkflowInvocationIntent = "route" | "mutate" | "review" | "evaluate" | "report";
|
|
143
|
+
export interface WorkflowInvocationRef {
|
|
144
|
+
kind: string;
|
|
145
|
+
id?: string;
|
|
146
|
+
path?: string;
|
|
147
|
+
url?: string;
|
|
148
|
+
dedupeKey?: string;
|
|
149
|
+
raw?: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
export interface WorkflowInvocationScope {
|
|
152
|
+
projectPath?: string;
|
|
153
|
+
projectGroup?: string;
|
|
154
|
+
worktreePolicy?: AgentWorktreeMode;
|
|
155
|
+
permissions?: string;
|
|
156
|
+
accountPolicy?: string;
|
|
157
|
+
concurrencyGroup?: string;
|
|
158
|
+
[key: string]: unknown;
|
|
159
|
+
}
|
|
160
|
+
export interface WorkflowInvocationOutputPolicy {
|
|
161
|
+
report?: "always" | "on_change" | "on_failure";
|
|
162
|
+
createTask?: "never" | "on_actionable" | "on_failure" | "always";
|
|
163
|
+
[key: string]: unknown;
|
|
164
|
+
}
|
|
165
|
+
export interface WorkflowInvocation {
|
|
166
|
+
id: string;
|
|
167
|
+
workflowId?: string;
|
|
168
|
+
templateId?: string;
|
|
169
|
+
sourceRef: WorkflowInvocationRef;
|
|
170
|
+
subjectRef: WorkflowInvocationRef;
|
|
171
|
+
intent: WorkflowInvocationIntent;
|
|
172
|
+
scope?: WorkflowInvocationScope;
|
|
173
|
+
outputPolicy?: WorkflowInvocationOutputPolicy;
|
|
174
|
+
createdAt: string;
|
|
175
|
+
updatedAt: string;
|
|
176
|
+
}
|
|
177
|
+
export interface CreateWorkflowInvocationInput {
|
|
178
|
+
id?: string;
|
|
179
|
+
workflowId?: string;
|
|
180
|
+
templateId?: string;
|
|
181
|
+
sourceRef: WorkflowInvocationRef;
|
|
182
|
+
subjectRef: WorkflowInvocationRef;
|
|
183
|
+
intent: WorkflowInvocationIntent;
|
|
184
|
+
scope?: WorkflowInvocationScope;
|
|
185
|
+
outputPolicy?: WorkflowInvocationOutputPolicy;
|
|
186
|
+
}
|
|
187
|
+
export type WorkflowWorkItemStatus = "queued" | "deferred" | "admitted" | "running" | "succeeded" | "failed" | "dead_letter" | "cancelled";
|
|
188
|
+
export interface WorkflowWorkItem {
|
|
189
|
+
id: string;
|
|
190
|
+
routeKey: string;
|
|
191
|
+
idempotencyKey: string;
|
|
192
|
+
invocationId: string;
|
|
193
|
+
sourceType: string;
|
|
194
|
+
sourceRef: string;
|
|
195
|
+
subjectRef: string;
|
|
196
|
+
projectKey?: string;
|
|
197
|
+
projectGroup?: string;
|
|
198
|
+
priority: number;
|
|
199
|
+
status: WorkflowWorkItemStatus;
|
|
200
|
+
attempts: number;
|
|
201
|
+
nextAttemptAt?: string;
|
|
202
|
+
leaseExpiresAt?: string;
|
|
203
|
+
workflowId?: string;
|
|
204
|
+
loopId?: string;
|
|
205
|
+
workflowRunId?: string;
|
|
206
|
+
lastReason?: string;
|
|
207
|
+
createdAt: string;
|
|
208
|
+
updatedAt: string;
|
|
209
|
+
}
|
|
210
|
+
export interface UpsertWorkflowWorkItemInput {
|
|
211
|
+
routeKey: string;
|
|
212
|
+
idempotencyKey: string;
|
|
213
|
+
invocationId: string;
|
|
214
|
+
sourceType: string;
|
|
215
|
+
sourceRef: string;
|
|
216
|
+
subjectRef: string;
|
|
217
|
+
projectKey?: string;
|
|
218
|
+
projectGroup?: string;
|
|
219
|
+
priority?: number;
|
|
220
|
+
status?: Extract<WorkflowWorkItemStatus, "queued" | "deferred">;
|
|
221
|
+
nextAttemptAt?: string;
|
|
222
|
+
lastReason?: string;
|
|
223
|
+
}
|
|
118
224
|
export interface WorkflowStep {
|
|
119
225
|
id: string;
|
|
120
226
|
name?: string;
|
|
@@ -164,8 +270,11 @@ export interface WorkflowRun {
|
|
|
164
270
|
workflowName: string;
|
|
165
271
|
loopId?: string;
|
|
166
272
|
loopRunId?: string;
|
|
273
|
+
invocationId?: string;
|
|
274
|
+
workItemId?: string;
|
|
167
275
|
scheduledFor?: string;
|
|
168
276
|
idempotencyKey?: string;
|
|
277
|
+
manifestPath?: string;
|
|
169
278
|
goalRunId?: string;
|
|
170
279
|
status: WorkflowRunStatus;
|
|
171
280
|
startedAt?: string;
|
package/docs/USAGE.md
CHANGED
|
@@ -108,7 +108,7 @@ loops create agent supply-chain-watch \
|
|
|
108
108
|
--provider codewith \
|
|
109
109
|
--every 15m \
|
|
110
110
|
--cwd /path/to/repo \
|
|
111
|
-
--sandbox
|
|
111
|
+
--sandbox workspace-write \
|
|
112
112
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
113
113
|
```
|
|
114
114
|
|
|
@@ -120,7 +120,7 @@ loops create agent supply-chain-watch \
|
|
|
120
120
|
--auth-profile account001 \
|
|
121
121
|
--every 15m \
|
|
122
122
|
--cwd /path/to/repo \
|
|
123
|
-
--sandbox
|
|
123
|
+
--sandbox workspace-write \
|
|
124
124
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
125
125
|
```
|
|
126
126
|
|
|
@@ -216,6 +216,9 @@ Built-in templates turn common orchestration flows into reusable workflow JSON.
|
|
|
216
216
|
`event-worker-verifier` handles any Hasna event envelope and then verifies the
|
|
217
217
|
handling. `bounded-agent-worker-verifier` is for recurring bounded agent work:
|
|
218
218
|
one worker runs a narrow objective, then a fresh verifier audits the result.
|
|
219
|
+
The catalog also includes `task-lifecycle`, `pr-review`, `scheduled-audit`,
|
|
220
|
+
`knowledge-refresh`, `report-only`, `incident-response`, and
|
|
221
|
+
`deterministic-check-create-task` for common operator workflows.
|
|
219
222
|
|
|
220
223
|
```bash
|
|
221
224
|
loops templates list
|
|
@@ -225,7 +228,7 @@ loops templates render todos-task-worker-verifier \
|
|
|
225
228
|
--var projectPath=/path/to/repo \
|
|
226
229
|
--var provider=codewith \
|
|
227
230
|
--var authProfilePool=account004,account005,account006 \
|
|
228
|
-
--var sandbox=
|
|
231
|
+
--var sandbox=workspace-write
|
|
229
232
|
loops templates create-workflow todos-task-worker-verifier \
|
|
230
233
|
--var taskId=<task-id> \
|
|
231
234
|
--var projectPath=/path/to/repo
|
|
@@ -240,15 +243,23 @@ loops templates render bounded-agent-worker-verifier \
|
|
|
240
243
|
--var projectPath=/path/to/repo \
|
|
241
244
|
--var provider=codewith \
|
|
242
245
|
--var authProfilePool=account004,account005 \
|
|
243
|
-
--var sandbox=
|
|
246
|
+
--var sandbox=workspace-write
|
|
247
|
+
loops templates render pr-review \
|
|
248
|
+
--var prUrl=https://github.com/hasna/loops/pull/123 \
|
|
249
|
+
--var projectPath=/path/to/repo
|
|
250
|
+
loops templates render deterministic-check-create-task \
|
|
251
|
+
--var projectPath=/path/to/repo \
|
|
252
|
+
--var checkCommand='your deterministic check and todos upsert command'
|
|
244
253
|
```
|
|
245
254
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
worktree
|
|
250
|
-
|
|
251
|
-
|
|
255
|
+
Repo-mutating task/event routes should set `worktreeMode=required` so the
|
|
256
|
+
workflow fails fast instead of falling back to the main checkout. When
|
|
257
|
+
`projectPath` is an existing git repository, OpenLoops inserts a
|
|
258
|
+
`prepare-worktree` command step before the worker and runs the worker/verifier
|
|
259
|
+
from a deterministic worktree under `~/.hasna/loops/worktrees/<repo>/<run>`.
|
|
260
|
+
The generated agent target includes worktree metadata (`mode`, `cwd`, `path`,
|
|
261
|
+
`branch`, `originalCwd`) so dry-runs and workflow inspection expose the exact
|
|
262
|
+
checkout.
|
|
252
263
|
|
|
253
264
|
Use explicit main/default checkout mode only when the task truly requires it:
|
|
254
265
|
|
|
@@ -259,21 +270,23 @@ loops templates render todos-task-worker-verifier \
|
|
|
259
270
|
--var worktreeMode=main
|
|
260
271
|
```
|
|
261
272
|
|
|
262
|
-
Use `worktreeMode=
|
|
273
|
+
Use `worktreeMode=auto` only for compatibility or mixed routes where a
|
|
274
|
+
non-git/non-mutating project is expected and the fallback is recorded. Use
|
|
263
275
|
`worktreeMode=off` for non-git projects. `worktreeRoot` and
|
|
264
276
|
`worktreeBranchPrefix` can override the storage root and branch prefix.
|
|
265
277
|
|
|
266
278
|
For event-driven task automation, `loops events handle todos-task` reads a
|
|
267
|
-
Hasna event envelope from stdin or `HASNA_EVENT_JSON`,
|
|
268
|
-
|
|
279
|
+
Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
|
|
280
|
+
`WorkflowInvocation`, upserts an admission work item, and admits that work item
|
|
281
|
+
into a deduped one-shot workflow loop when route capacity allows:
|
|
269
282
|
|
|
270
283
|
```bash
|
|
271
284
|
cat task-created-event.json | loops events handle todos-task \
|
|
272
285
|
--provider codewith \
|
|
273
286
|
--auth-profile-pool account004,account005,account006 \
|
|
274
287
|
--permission-mode bypass \
|
|
275
|
-
--sandbox
|
|
276
|
-
--worktree-mode
|
|
288
|
+
--sandbox workspace-write \
|
|
289
|
+
--worktree-mode required
|
|
277
290
|
```
|
|
278
291
|
|
|
279
292
|
Task routing is explicit opt-in. The handler skips the event without creating a
|
|
@@ -297,19 +310,40 @@ cat task-created-event.json | loops events handle todos-task \
|
|
|
297
310
|
--max-active 12
|
|
298
311
|
```
|
|
299
312
|
|
|
300
|
-
The limits count active
|
|
313
|
+
The limits count active admitted/running OpenLoops work items once per workflow.
|
|
301
314
|
`--max-active-per-project` gates new work for the same project path,
|
|
302
315
|
`--max-active-per-project-group` shares a pool across related projects such as
|
|
303
316
|
`oss`, and `--max-active` is the global routed-workflow cap. Project matching
|
|
304
317
|
uses the canonical git top-level path when available, so repo subdirectories
|
|
305
|
-
share the same project cap. A throttled event
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
by task/event id before rendering worktree plans or checking route limits. In
|
|
318
|
+
share the same project cap. A throttled event records a deferred OpenLoops
|
|
319
|
+
admission work item with JSON evidence instead of creating another worker loop.
|
|
320
|
+
Re-delivering the event later is safe because handlers dedupe by the work-item
|
|
321
|
+
idempotency key before rendering worktree plans or checking route limits. In
|
|
310
322
|
dry-run mode, throttle counts are not evaluated because opening the live loop
|
|
311
323
|
store can create or migrate the local database.
|
|
312
324
|
|
|
325
|
+
Inspect route state with:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
cat task-created-event.json | loops routes preview todos-task --sandbox workspace-write
|
|
329
|
+
cat task-created-event.json | loops routes create todos-task --sandbox workspace-write
|
|
330
|
+
loops routes drain todos-task --task-list oss --max-dispatch 2 --compact
|
|
331
|
+
loops routes schedule todos-task route-drain-oss-5m --every 5m --task-list oss --max-dispatch 1 --compact
|
|
332
|
+
loops routes list --route-key todos-task
|
|
333
|
+
loops routes show <work-item-id>
|
|
334
|
+
loops routes invocations
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
When a workflow run starts from an admitted work item, OpenLoops writes a
|
|
338
|
+
manifest under:
|
|
339
|
+
|
|
340
|
+
```text
|
|
341
|
+
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/manifest.json
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`subject-key` is a safe derived path segment (`kind-safeSlug-shortHash`), not
|
|
345
|
+
the raw subject reference. The raw `subjectRef` is stored inside the manifest.
|
|
346
|
+
|
|
313
347
|
When tasks were created while capacity was full, or when bulk producers created
|
|
314
348
|
many tasks at once, use the drain command instead of replaying every webhook by
|
|
315
349
|
hand. It scans `todos ready --json`, so tasks with incomplete dependencies,
|
|
@@ -329,7 +363,7 @@ loops events drain todos-task \
|
|
|
329
363
|
--max-active-per-project 1 \
|
|
330
364
|
--max-active-per-project-group 4 \
|
|
331
365
|
--max-active 12 \
|
|
332
|
-
--worktree-mode
|
|
366
|
+
--worktree-mode required \
|
|
333
367
|
--evidence-dir /home/hasna/.hasna/loops/reports/task-drain
|
|
334
368
|
```
|
|
335
369
|
|
|
@@ -354,19 +388,26 @@ cat event.json | loops events handle generic \
|
|
|
354
388
|
--provider codewith \
|
|
355
389
|
--auth-profile-pool account004,account005,account006 \
|
|
356
390
|
--permission-mode bypass \
|
|
357
|
-
--sandbox
|
|
358
|
-
--project-path /path/to/repo
|
|
391
|
+
--sandbox workspace-write \
|
|
392
|
+
--project-path /path/to/repo \
|
|
393
|
+
--worktree-mode required
|
|
359
394
|
```
|
|
360
395
|
|
|
361
396
|
This is the intended deterministic-to-agentic path: a producer creates a todos
|
|
362
|
-
task, `@hasna/events` delivers `task.created`, OpenLoops
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
397
|
+
task, `@hasna/events` delivers `task.created`, OpenLoops records the invocation
|
|
398
|
+
and admission item, OpenLoops creates a worker/verifier workflow when admitted,
|
|
399
|
+
and the workflow updates todos with evidence. Use account pools so worker and
|
|
400
|
+
verifier steps do not burn the same profile; OpenLoops picks deterministically
|
|
401
|
+
and uses a different verifier profile when the pool has at least two entries.
|
|
402
|
+
Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
|
|
403
|
+
loop input without storing anything, including the worktree path and branch for
|
|
368
404
|
git-backed tasks.
|
|
369
405
|
|
|
406
|
+
Generated worker/verifier workflows fail closed when `sandbox=danger-full-access`
|
|
407
|
+
is requested without `manualBreakGlass=true`. Use `workspace-write` for
|
|
408
|
+
unattended task/event routes. Full access is an explicit manual emergency path,
|
|
409
|
+
not a default automation mode.
|
|
410
|
+
|
|
370
411
|
## Transcript-Driven Loops
|
|
371
412
|
|
|
372
413
|
OpenLoops can turn long-form media or meeting transcripts into recurring workflow work when paired with `iapp-transcriber`. The template at `docs/workflows/transcript-feedback-to-loops.json` transcribes an authorized media URL, asks an agent to extract recurring loop candidates, authors workflow specs, and validates generated workflows before scheduling. Copy it into the target repo, replace `/path/to/repo` with that repo's absolute path, and provide `TRANSCRIBER_SOURCE_URL` through the runner environment or a private, uncommitted workflow copy before storing or scheduling it. Do not commit private or signed media URLs.
|
package/package.json
CHANGED