@hasna/loops 0.3.38 → 0.3.40
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 +1491 -176
- package/dist/daemon/index.js +499 -43
- package/dist/index.d.ts +1 -1
- package/dist/index.js +823 -53
- package/dist/lib/format.d.ts +3 -1
- package/dist/lib/run-artifacts.d.ts +15 -0
- package/dist/lib/store.d.ts +34 -1
- package/dist/lib/store.js +424 -8
- package/dist/lib/templates.d.ts +10 -0
- package/dist/sdk/index.d.ts +2 -1
- package/dist/sdk/index.js +529 -33
- package/dist/types.d.ts +109 -0
- package/docs/USAGE.md +57 -20
- 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,7 +243,13 @@ 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
255
|
Repo-mutating task/event routes should set `worktreeMode=required` so the
|
|
@@ -267,15 +276,16 @@ non-git/non-mutating project is expected and the fallback is recorded. Use
|
|
|
267
276
|
`worktreeBranchPrefix` can override the storage root and branch prefix.
|
|
268
277
|
|
|
269
278
|
For event-driven task automation, `loops events handle todos-task` reads a
|
|
270
|
-
Hasna event envelope from stdin or `HASNA_EVENT_JSON`,
|
|
271
|
-
|
|
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:
|
|
272
282
|
|
|
273
283
|
```bash
|
|
274
284
|
cat task-created-event.json | loops events handle todos-task \
|
|
275
285
|
--provider codewith \
|
|
276
286
|
--auth-profile-pool account004,account005,account006 \
|
|
277
287
|
--permission-mode bypass \
|
|
278
|
-
--sandbox
|
|
288
|
+
--sandbox workspace-write \
|
|
279
289
|
--worktree-mode required
|
|
280
290
|
```
|
|
281
291
|
|
|
@@ -300,19 +310,40 @@ cat task-created-event.json | loops events handle todos-task \
|
|
|
300
310
|
--max-active 12
|
|
301
311
|
```
|
|
302
312
|
|
|
303
|
-
The limits count active
|
|
313
|
+
The limits count active admitted/running OpenLoops work items once per workflow.
|
|
304
314
|
`--max-active-per-project` gates new work for the same project path,
|
|
305
315
|
`--max-active-per-project-group` shares a pool across related projects such as
|
|
306
316
|
`oss`, and `--max-active` is the global routed-workflow cap. Project matching
|
|
307
317
|
uses the canonical git top-level path when available, so repo subdirectories
|
|
308
|
-
share the same project cap. A throttled event
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
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
|
|
313
322
|
dry-run mode, throttle counts are not evaluated because opening the live loop
|
|
314
323
|
store can create or migrate the local database.
|
|
315
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
|
+
|
|
316
347
|
When tasks were created while capacity was full, or when bulk producers created
|
|
317
348
|
many tasks at once, use the drain command instead of replaying every webhook by
|
|
318
349
|
hand. It scans `todos ready --json`, so tasks with incomplete dependencies,
|
|
@@ -357,20 +388,26 @@ cat event.json | loops events handle generic \
|
|
|
357
388
|
--provider codewith \
|
|
358
389
|
--auth-profile-pool account004,account005,account006 \
|
|
359
390
|
--permission-mode bypass \
|
|
360
|
-
--sandbox
|
|
391
|
+
--sandbox workspace-write \
|
|
361
392
|
--project-path /path/to/repo \
|
|
362
393
|
--worktree-mode required
|
|
363
394
|
```
|
|
364
395
|
|
|
365
396
|
This is the intended deterministic-to-agentic path: a producer creates a todos
|
|
366
|
-
task, `@hasna/events` delivers `task.created`, OpenLoops
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
|
372
404
|
git-backed tasks.
|
|
373
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
|
+
|
|
374
411
|
## Transcript-Driven Loops
|
|
375
412
|
|
|
376
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