@agentproto/worktree 0.4.1 → 0.4.3
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 +25 -0
- package/dist/bin/worktree-agent.mjs +2 -2
- package/dist/{chunk-ZRTFECIP.mjs → chunk-OFO7QHCW.mjs} +42 -21
- package/dist/chunk-OFO7QHCW.mjs.map +1 -0
- package/dist/{chunk-AJJPABO3.mjs → chunk-PKV55ERJ.mjs} +3 -3
- package/dist/{chunk-AJJPABO3.mjs.map → chunk-PKV55ERJ.mjs.map} +1 -1
- package/dist/index.d.ts +36 -9
- package/dist/index.mjs +11 -10
- package/dist/index.mjs.map +1 -1
- package/dist/provider/index.mjs +1 -1
- package/package.json +7 -5
- package/routines/README.md +113 -0
- package/routines/worktree-gc/ROUTINE.md +96 -0
- package/routines/worktree-gc-notify/ROUTINE.md +70 -0
- package/routines/worktree-gc-notify/WORKFLOW.md +70 -0
- package/routines/worktree-gc-notify/entry.mjs +77 -0
- package/dist/chunk-ZRTFECIP.mjs.map +0 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Routines here have no runtime yet — a bridge proposal (DESIGN ONLY)
|
|
2
|
+
|
|
3
|
+
`worktree-gc/ROUTINE.md` in this directory is a real, schema-valid AIP-41
|
|
4
|
+
routine manifest (`schema: routine/v1`), shipped `enabled: false` since PR
|
|
5
|
+
#626. Nothing in this repo turns it into a scheduled fire. This note records
|
|
6
|
+
the gap precisely and sketches a bridge — **not built, not decided**; the
|
|
7
|
+
call on whether/how to build it belongs to Jeremy.
|
|
8
|
+
|
|
9
|
+
## The gap, precisely
|
|
10
|
+
|
|
11
|
+
- `@agentproto/routine` (`packages/routine/src`) parses and validates a
|
|
12
|
+
`ROUTINE.md` — `parseRoutineManifest` → `routineFromManifest` →
|
|
13
|
+
`defineRoutine` (`define-routine.ts`) — into a frozen `RoutineHandle`. That
|
|
14
|
+
handle is **pure data**. `defineRoutine`'s `build()` just spreads the
|
|
15
|
+
validated definition into a new object; there is no `execute()`, no
|
|
16
|
+
scheduling hook, nothing that fires anything.
|
|
17
|
+
- Nothing scans a workspace's `.routines/` directory. `grep`ing the whole
|
|
18
|
+
`packages/` tree for a reader of `.routines/*/ROUTINE.md` outside this
|
|
19
|
+
package's own manifest parser and its tests turns up nothing.
|
|
20
|
+
- The one LIVE scheduling primitive in the daemon is
|
|
21
|
+
`packages/runtime/src/cron-scheduler.ts`'s `CronScheduler` — persisted,
|
|
22
|
+
ticks every 20s, fires a `CronAction`. Its `CronAction` union
|
|
23
|
+
(`cron-scheduler.ts:50-72`) has exactly three kinds: `command` (spawn a
|
|
24
|
+
shell command), `agent` (spawn a fresh agent session), `prompt-session`
|
|
25
|
+
(reprompt an existing one). **None of them is "call an in-process
|
|
26
|
+
AIP-14 tool by id."**
|
|
27
|
+
- AIP-41's own `TargetTool` (`packages/routine/src/types.ts:163-171`) is
|
|
28
|
+
exactly that: `{ tool: string, inputs?: {...} }` — a TOOL ref, not a shell
|
|
29
|
+
command or an agent prompt. There is no `CronAction` shape it maps onto
|
|
30
|
+
today.
|
|
31
|
+
|
|
32
|
+
That mismatch is why the daily worktree-gc sweep operationalized today as a
|
|
33
|
+
`kind:"command"` cron job (`worktree-janitor-daily`) that shells out to
|
|
34
|
+
`agentproto worktree gc --apply` — a subprocess, re-spawning a whole CLI
|
|
35
|
+
process, re-resolving `git` from scratch — instead of the daemon just
|
|
36
|
+
calling its own already-registered `worktree_gc` tool in-process, the way
|
|
37
|
+
`worktree_gc` MCP calls do it today. That subprocess indirection is exactly
|
|
38
|
+
where Fix A's `spawn git ENOENT` (a narrow inherited PATH baked into the
|
|
39
|
+
launchd plist) came from. A `kind:"tool"` bridge doesn't just close the
|
|
40
|
+
routine-runtime gap — it removes the entire class of "spawn a CLI to call a
|
|
41
|
+
tool the daemon already hosts" bugs for anything wired through it.
|
|
42
|
+
|
|
43
|
+
## Proposed bridge (not built)
|
|
44
|
+
|
|
45
|
+
1. **New `CronAction` kind: `"tool"`.**
|
|
46
|
+
```ts
|
|
47
|
+
| { kind: "tool"; tool: string; inputs?: Record<string, unknown> }
|
|
48
|
+
```
|
|
49
|
+
Dispatched in-process against the same tool registry the daemon's MCP
|
|
50
|
+
surface already calls `worktree_gc` through — no subprocess, so no PATH
|
|
51
|
+
to get wrong. Whether an argv-prefix-style allowlist (mirroring
|
|
52
|
+
`command-tools.ts`'s `AllowlistEntry`) should gate which tool ids a cron
|
|
53
|
+
job may target is an open question below, not resolved here.
|
|
54
|
+
|
|
55
|
+
2. **A registrar** — a new module, e.g.
|
|
56
|
+
`packages/runtime/src/routine-cron-bridge.ts` — that:
|
|
57
|
+
- Scans `<workspace>/.routines/*/ROUTINE.md` (the path the existing
|
|
58
|
+
`worktree-gc/ROUTINE.md` doc already documents as the install target).
|
|
59
|
+
- Parses each with `@agentproto/routine`'s existing
|
|
60
|
+
`parseRoutineManifest` / `routineFromManifest` — reuse, not a second
|
|
61
|
+
parser.
|
|
62
|
+
- Keeps only `enabled: true` entries whose `schedule.kind === "cron"`
|
|
63
|
+
(interval/calendar/manual/event schedules are out of scope for a
|
|
64
|
+
*cron*-scheduler bridge by construction; see open questions).
|
|
65
|
+
- Maps `target`: a `TargetTool` becomes `{kind:"tool", tool, inputs}`
|
|
66
|
+
directly. A `TargetAction` or `TargetWorkflow` target has no obvious
|
|
67
|
+
`CronAction` mapping yet — v1 logs "unsupported target kind" and skips
|
|
68
|
+
rather than guessing.
|
|
69
|
+
- Calls `cronScheduler.create(...)`, keyed so a re-scan **upserts**
|
|
70
|
+
rather than accumulates duplicates — needs a stable identifier
|
|
71
|
+
(e.g. a `sourceRoutineId` field added to `CronJob`, or a deterministic
|
|
72
|
+
`label: "routine:<id>"` the registrar diffs against) so editing or
|
|
73
|
+
removing a `ROUTINE.md` is reflected, not just additive.
|
|
74
|
+
- `@agentproto/routine`'s only deps are `gray-matter` + `zod` (checked:
|
|
75
|
+
`packages/routine/package.json`) — light enough that `@agentproto/runtime`
|
|
76
|
+
can depend on it directly, unlike `@agentproto/worktree`, which
|
|
77
|
+
`worktree-gc.ts`'s own docblock deliberately keeps out of the runtime's
|
|
78
|
+
dependency graph as "the heavy dependency."
|
|
79
|
+
|
|
80
|
+
3. **Trigger points**: run the scan once at daemon boot (`serve.ts`, next to
|
|
81
|
+
where `cron-scheduler.ts` is already constructed), plus a new explicit
|
|
82
|
+
verb (`agentproto routine reload` / a `routines_reload` MCP tool,
|
|
83
|
+
mirroring the existing `cron_list`/`cron_create` surface) so editing a
|
|
84
|
+
`.routines/*.md` doesn't require a daemon restart to take effect.
|
|
85
|
+
|
|
86
|
+
## Open questions (Jeremy's call, not resolved here)
|
|
87
|
+
|
|
88
|
+
- **Retry / failure routing.** `CronJob` has no `retry` or `on_failure`
|
|
89
|
+
fields today (`cron-scheduler.ts:74-88`) — a fire either succeeds or
|
|
90
|
+
throws once. AIP-41's `retry.max_attempts` / `on_failure.create_work_item`
|
|
91
|
+
/ `fires_events` (AIP-37 vocabulary) have no home on the cron side yet.
|
|
92
|
+
`worktree-gc/ROUTINE.md`'s own `retry.max_attempts: 1` happens to already
|
|
93
|
+
match cron's today-single-attempt behavior, which is why this particular
|
|
94
|
+
routine doesn't surface the gap — a routine that actually wants retries
|
|
95
|
+
or `fires_events` would.
|
|
96
|
+
- **Allowlisting `kind:"tool"` cron jobs.** `kind:"command"` gates through
|
|
97
|
+
`.agentproto/allowed-commands.json`. Should an in-process tool call get an
|
|
98
|
+
analogous allowlist, or is "the tool is already registered on this
|
|
99
|
+
daemon" sufficient authorization? Registered tools can mutate a lot
|
|
100
|
+
(`worktree_gc --apply` deletes branches and directories) — this deserves
|
|
101
|
+
its own decision, not a default inherited from the command-allowlist
|
|
102
|
+
model by accident.
|
|
103
|
+
- **Where the bridge module lives.** Proposed above as
|
|
104
|
+
`packages/runtime/src/routine-cron-bridge.ts`, but it could equally live
|
|
105
|
+
in the CLI host (next to `makeWorktreeGcRunner`) if the intent is to keep
|
|
106
|
+
`packages/runtime` free of anything that isn't a pure port/injection
|
|
107
|
+
boundary.
|
|
108
|
+
- **Non-cron schedules** (`interval`, `calendar`, `manual`, `event`) aren't
|
|
109
|
+
addressed at all — this proposal only closes the `schedule.kind: "cron"`
|
|
110
|
+
case, which is what `worktree-gc/ROUTINE.md` uses.
|
|
111
|
+
- **Multi-workspace scope.** v1 above only scans the active workspace's
|
|
112
|
+
`.routines/`, matching how `worktree_status` / `worktree_gc` themselves
|
|
113
|
+
resolve a repo today (`resolveWorktreeQueryRoot` → `getActiveWorkspace`).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema: routine/v1
|
|
3
|
+
id: worktree-gc
|
|
4
|
+
description: |
|
|
5
|
+
Scheduled garbage collection of merged/integrated git worktrees. Fires the
|
|
6
|
+
`worktree_gc` tool with `apply: true` so worktrees whose branch is merged
|
|
7
|
+
(or fresh) and whose tree is clean are reclaimed hands-off — the worktree is
|
|
8
|
+
removed and its branch deleted. An OPEN PR, live sessions, or anything
|
|
9
|
+
unresolved is always held and never touched; a dirty-but-integrated worktree
|
|
10
|
+
is left in place unless `salvageDirty` is turned on (it is not, here).
|
|
11
|
+
Ships DISABLED (`enabled: false`) — install it into a workspace's `.routines/`
|
|
12
|
+
and flip `enabled: true` to activate.
|
|
13
|
+
version: "1.0.0"
|
|
14
|
+
schedule:
|
|
15
|
+
kind: cron
|
|
16
|
+
cron: "0 4 * * *"
|
|
17
|
+
timezone: "UTC"
|
|
18
|
+
catchup: skip
|
|
19
|
+
target:
|
|
20
|
+
tool: worktree_gc
|
|
21
|
+
inputs:
|
|
22
|
+
apply: true
|
|
23
|
+
salvageDirty: false
|
|
24
|
+
retry:
|
|
25
|
+
max_attempts: 1
|
|
26
|
+
backoff: fixed
|
|
27
|
+
on_failure:
|
|
28
|
+
create_work_item: true
|
|
29
|
+
fire_event: worktree.gc.failed
|
|
30
|
+
fires_events:
|
|
31
|
+
- worktree.gc.completed
|
|
32
|
+
- worktree.gc.failed
|
|
33
|
+
enabled: false
|
|
34
|
+
tags: [worktree, gc, maintenance]
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Worktree GC Routine
|
|
38
|
+
|
|
39
|
+
Runs daily at **04:00 UTC** and invokes the `worktree_gc` tool (shipped in
|
|
40
|
+
[`@agentproto/worktree`](../../README.md), exposed over MCP + HTTP by the
|
|
41
|
+
daemon) with `apply: true`. This reaps merged worktrees automatically so a
|
|
42
|
+
long-running workspace doesn't accumulate stale `_worktrees/*` trees and their
|
|
43
|
+
dead `wt/*` branches.
|
|
44
|
+
|
|
45
|
+
## What it does
|
|
46
|
+
|
|
47
|
+
Each fire re-runs the same plan/apply engine (`planGc` / `applyGc` in
|
|
48
|
+
`packages/worktree/src/gc.ts`) that backs `agentproto worktree gc --apply`:
|
|
49
|
+
|
|
50
|
+
- **`reclaim`** — branch is merged (or fresh/zero-commit) **and** the tree is
|
|
51
|
+
clean → the worktree is removed and its branch deleted.
|
|
52
|
+
- **`salvage`** — integrated but the tree is dirty → **held** here, because
|
|
53
|
+
`salvageDirty` is `false`. Turn that input on only if you want dirty
|
|
54
|
+
integrated worktrees archived (snapshot-then-remove) rather than left alone.
|
|
55
|
+
- **`hold`** — an open PR, live sessions, or anything unresolved → never
|
|
56
|
+
touched, regardless of any flag.
|
|
57
|
+
|
|
58
|
+
Every entry is re-classified from scratch immediately before it is acted on,
|
|
59
|
+
so a plan that has gone stale between planning and applying is refused rather
|
|
60
|
+
than acted on.
|
|
61
|
+
|
|
62
|
+
## Safety invariants (inherited from the engine)
|
|
63
|
+
|
|
64
|
+
These are enforced by the engine, not by this manifest — the routine cannot
|
|
65
|
+
weaken them:
|
|
66
|
+
|
|
67
|
+
- **Merge-gated reclaim.** Teardown only fires when integration ∈
|
|
68
|
+
{merged, fresh} **and** the tree is clean.
|
|
69
|
+
- **Open = hold.** A worktree with an open PR or live sessions is always held.
|
|
70
|
+
- **Dirty = salvage-only.** A dirty integrated worktree is only ever archived
|
|
71
|
+
(never silently discarded), and only when `salvageDirty` is `true`.
|
|
72
|
+
|
|
73
|
+
## Repo scope at fire time
|
|
74
|
+
|
|
75
|
+
`target.inputs` carries no `repoRoot`, so the daemon resolves the repo to gc
|
|
76
|
+
from the **active workspace** (`resolveWorktreeQueryRoot` →
|
|
77
|
+
`getActiveWorkspace`). Pin a specific repo by adding `repoRoot: <abs path>` or
|
|
78
|
+
`workspaceSlug: <slug>` to `target.inputs` before enabling — `repoRoot` wins
|
|
79
|
+
over `workspaceSlug`, which wins over the active workspace.
|
|
80
|
+
|
|
81
|
+
## Enabling
|
|
82
|
+
|
|
83
|
+
This routine is opt-in. To activate it in a workspace:
|
|
84
|
+
|
|
85
|
+
1. Copy this directory to the workspace's routine library —
|
|
86
|
+
`<workspace>/.routines/worktree-gc/ROUTINE.md` (the path
|
|
87
|
+
`@agentproto/routine`'s `routineSpec.pathOf` expects).
|
|
88
|
+
2. Set `enabled: true` in the frontmatter (and, if desired, pin `repoRoot` /
|
|
89
|
+
`workspaceSlug` per above).
|
|
90
|
+
3. Reload routines so the daemon registers the schedule.
|
|
91
|
+
|
|
92
|
+
## Failure routing
|
|
93
|
+
|
|
94
|
+
If a fire fails, `retry` gives it a single attempt (no backoff), then
|
|
95
|
+
`on_failure` opens a work item and fires `worktree.gc.failed`. A clean run
|
|
96
|
+
fires `worktree.gc.completed`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema: routine/v1
|
|
3
|
+
id: worktree-gc-notify
|
|
4
|
+
description: |
|
|
5
|
+
Scheduled worktree gc + Telegram notification, as ONE `target.workflow`
|
|
6
|
+
routine instead of a bare `target.tool` fire — the sibling `worktree-gc`
|
|
7
|
+
routine (`../worktree-gc/ROUTINE.md`) gc's but never reports out; this
|
|
8
|
+
fires `worktree-gc-notify/WORKFLOW.md`, whose three tool steps gc, format,
|
|
9
|
+
and notify hosted agentpush in one run. Ships DISABLED (`enabled: false`)
|
|
10
|
+
— install it into a workspace's `.routines/` and flip `enabled: true` to
|
|
11
|
+
activate. See the WORKFLOW.md alongside this file for what each step does.
|
|
12
|
+
version: "1.0.0"
|
|
13
|
+
schedule:
|
|
14
|
+
kind: cron
|
|
15
|
+
cron: "0 4 * * *"
|
|
16
|
+
timezone: "UTC"
|
|
17
|
+
catchup: skip
|
|
18
|
+
target:
|
|
19
|
+
workflow:
|
|
20
|
+
file: /Volumes/SSDExternalMacStudio/Code/products/agentik/agentik-studio/projects/agentproto/ts/packages/worktree/routines/worktree-gc-notify/WORKFLOW.md
|
|
21
|
+
inputs:
|
|
22
|
+
chatId: "<REPLACE_WITH_YOUR_TELEGRAM_CHAT_ID>"
|
|
23
|
+
retry:
|
|
24
|
+
max_attempts: 1
|
|
25
|
+
backoff: fixed
|
|
26
|
+
on_failure:
|
|
27
|
+
create_work_item: true
|
|
28
|
+
fire_event: worktree.gc-notify.failed
|
|
29
|
+
fires_events:
|
|
30
|
+
- worktree.gc-notify.completed
|
|
31
|
+
- worktree.gc-notify.failed
|
|
32
|
+
enabled: false
|
|
33
|
+
tags: [worktree, gc, maintenance, notify]
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Worktree GC + notify routine
|
|
37
|
+
|
|
38
|
+
Runs daily at **04:00 UTC**, firing `worktree-gc-notify/WORKFLOW.md` via
|
|
39
|
+
`workflow_run_file` (the same lowering every `target.workflow.file` routine
|
|
40
|
+
goes through — see `routine-registrar.ts`'s `routineTargetToToolCall`). That
|
|
41
|
+
workflow's three tool steps (`gc` → `format` → `notify`) are documented in
|
|
42
|
+
the WORKFLOW.md itself.
|
|
43
|
+
|
|
44
|
+
## Why a workflow target instead of `target.tool`
|
|
45
|
+
|
|
46
|
+
`../worktree-gc/ROUTINE.md` already fires `worktree_gc` directly as a bare
|
|
47
|
+
`target.tool` — simplest form, no reporting. This routine dogfoods the OTHER
|
|
48
|
+
target shape (`target.workflow`), which needed the daemon-side
|
|
49
|
+
`compileWorkflow` tool-registry fix to run at all: before that fix, ANY
|
|
50
|
+
`tool` step in a WORKFLOW.md (this one has three) failed to resolve
|
|
51
|
+
("no tool registered"), so a `target.workflow` routine whose workflow used
|
|
52
|
+
tool steps could never actually complete.
|
|
53
|
+
|
|
54
|
+
## Enabling
|
|
55
|
+
|
|
56
|
+
1. Copy this directory to `<workspace>/.routines/worktree-gc-notify/`
|
|
57
|
+
(ROUTINE.md + WORKFLOW.md + entry.mjs together).
|
|
58
|
+
2. Allowlist `bash` in that workspace's `.agentproto/allowed-commands.json`
|
|
59
|
+
(`command_execute`'s `notify` step needs it) — see the WORKFLOW.md.
|
|
60
|
+
3. Set `enabled: true`, update `target.workflow.file` to wherever the
|
|
61
|
+
WORKFLOW.md actually lives in that environment, and replace
|
|
62
|
+
`target.inputs.chatId` with your own Telegram chat id.
|
|
63
|
+
4. Reload routines, or fire it once ad-hoc via `routine_trigger` without
|
|
64
|
+
waiting for the schedule or flipping `enabled`.
|
|
65
|
+
|
|
66
|
+
## Failure routing
|
|
67
|
+
|
|
68
|
+
Same convention as `worktree-gc`: one retry attempt (no backoff), then
|
|
69
|
+
`on_failure` opens a work item and fires `worktree.gc-notify.failed`. A
|
|
70
|
+
clean run fires `worktree.gc-notify.completed`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Worktree GC + notify
|
|
3
|
+
id: worktree-gc-notify
|
|
4
|
+
description: |
|
|
5
|
+
Garbage-collects merged/fresh+clean worktrees under agentproto/ts, then
|
|
6
|
+
reports the outcome to Telegram via the hosted agentpush `send_message`
|
|
7
|
+
tool. Dogfoods a real multi-tool-step WORKFLOW.md now that the daemon's
|
|
8
|
+
`compileWorkflow` resolves `tool` steps through `dispatchTool` (see
|
|
9
|
+
`packages/runtime/src/workflow-tool-registry.ts`) instead of failing every
|
|
10
|
+
`tool` step with "no tool registered". The `format` step is entry-based —
|
|
11
|
+
there is no string expression language for a `transform` step's `compute`
|
|
12
|
+
in the declarative manifest.
|
|
13
|
+
version: 0.1.0
|
|
14
|
+
entry: ./entry.mjs
|
|
15
|
+
inputs:
|
|
16
|
+
chatId:
|
|
17
|
+
type: string
|
|
18
|
+
description: Telegram chat id `notify` reports to. Operator-supplied — never hardcoded.
|
|
19
|
+
outputs: {}
|
|
20
|
+
steps:
|
|
21
|
+
- id: gc
|
|
22
|
+
kind: tool
|
|
23
|
+
tool: worktree_gc
|
|
24
|
+
inputs:
|
|
25
|
+
apply: true
|
|
26
|
+
salvageDirty: false
|
|
27
|
+
repoRoot: /Volumes/SSDExternalMacStudio/Code/products/agentik/agentik-studio/projects/agentproto/ts
|
|
28
|
+
- id: format
|
|
29
|
+
kind: transform
|
|
30
|
+
- id: notify
|
|
31
|
+
kind: tool
|
|
32
|
+
tool: command_execute
|
|
33
|
+
inputs:
|
|
34
|
+
command: bash
|
|
35
|
+
stdin: $steps.format
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
# Worktree GC + notify
|
|
39
|
+
|
|
40
|
+
Three tool steps, in order:
|
|
41
|
+
|
|
42
|
+
1. **`gc`** — `worktree_gc` with `apply: true`, `salvageDirty: false`. Same
|
|
43
|
+
safety invariants as the sibling `worktree-gc` routine (merge-gated
|
|
44
|
+
reclaim, open-PR-is-always-hold, dirty-is-salvage-only): this workflow
|
|
45
|
+
inherits them from the engine, it doesn't re-implement them.
|
|
46
|
+
2. **`format`** — an entry-based `transform` step (see `entry.mjs`) that
|
|
47
|
+
turns `$steps.gc`'s structured `{ mode, outcomes }` into the exact JSON
|
|
48
|
+
body hosted agentpush's `POST /tools/send_message` expects (mirrors
|
|
49
|
+
`.plans/agentproto-maintenance-crons/notify-worktree-gc.sh`'s formatting).
|
|
50
|
+
3. **`notify`** — `command_execute` running `bash -c '<curl to hosted
|
|
51
|
+
agentpush>'`, fed `format`'s output as `stdin` (`curl --data-binary @-`).
|
|
52
|
+
The LOCAL agentpush MCP is down; this goes straight to
|
|
53
|
+
`https://api.agentpush.io/tools/send_message` with the Bearer key sourced
|
|
54
|
+
from `envs/agentpush/.env.local`, same as the working cron script.
|
|
55
|
+
|
|
56
|
+
## Enabling
|
|
57
|
+
|
|
58
|
+
Ships wired to `routines/worktree-gc-notify/ROUTINE.md`, disabled
|
|
59
|
+
(`enabled: false`). To activate in a workspace:
|
|
60
|
+
|
|
61
|
+
1. Copy this directory to `<workspace>/.routines/worktree-gc-notify/`.
|
|
62
|
+
2. `command_execute` requires `bash` (and whatever `curl` needs) allowlisted
|
|
63
|
+
in that workspace's `.agentproto/allowed-commands.json`.
|
|
64
|
+
3. Set `enabled: true` on the copied `ROUTINE.md`, update
|
|
65
|
+
`target.workflow.file` if the workflow isn't installed at this same
|
|
66
|
+
absolute path in that environment (`workflow_run_file`'s `path` is read
|
|
67
|
+
as-is, with no anchoring to the routine's own directory), and set
|
|
68
|
+
`target.inputs.chatId` to your own Telegram chat id.
|
|
69
|
+
4. Reload routines so the daemon registers the schedule (or fire it once
|
|
70
|
+
ad-hoc via `routine_trigger`).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Runtime source of truth for WORKFLOW.md's step graph. The frontmatter
|
|
2
|
+
// mirrors this (id + kind) for governance, per `reconcileEntry`; `format`'s
|
|
3
|
+
// `compute` is a real function, which is why this step can only be
|
|
4
|
+
// entry-based — there is no string expression language for it in the
|
|
5
|
+
// declarative manifest.
|
|
6
|
+
export default {
|
|
7
|
+
name: "Worktree GC + notify",
|
|
8
|
+
id: "worktree-gc-notify",
|
|
9
|
+
description:
|
|
10
|
+
"Garbage-collect merged/fresh+clean worktrees, then report the outcome to Telegram via hosted agentpush.",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
// `chatId` is the Telegram chat id `notify` reports to — operator-supplied
|
|
13
|
+
// at invocation (routine `target.inputs` / `workflow_run_file`'s `input`),
|
|
14
|
+
// never hardcoded, since this workflow ships in a public repo.
|
|
15
|
+
inputs: { chatId: { type: "string" } },
|
|
16
|
+
outputs: {},
|
|
17
|
+
steps: [
|
|
18
|
+
{
|
|
19
|
+
id: "gc",
|
|
20
|
+
kind: "tool",
|
|
21
|
+
tool: "worktree_gc",
|
|
22
|
+
inputs: {
|
|
23
|
+
apply: true,
|
|
24
|
+
salvageDirty: false,
|
|
25
|
+
repoRoot:
|
|
26
|
+
"/Volumes/SSDExternalMacStudio/Code/products/agentik/agentik-studio/projects/agentproto/ts",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "format",
|
|
31
|
+
kind: "transform",
|
|
32
|
+
// Turns the `gc` step's structured result into the exact JSON body
|
|
33
|
+
// hosted agentpush's `POST /tools/send_message` expects. The `$steps.*`
|
|
34
|
+
// ref grammar can substitute `$steps.gc` whole into another step's
|
|
35
|
+
// inputs, but can't serialize an object into a plain string a later
|
|
36
|
+
// tool step's `stdin` can carry — that's what this step is for.
|
|
37
|
+
compute: (b) => {
|
|
38
|
+
const gc = b.steps.gc ?? {}
|
|
39
|
+
const outcomes = Array.isArray(gc.outcomes) ? gc.outcomes : []
|
|
40
|
+
const counts = {}
|
|
41
|
+
for (const o of outcomes) counts[o.result] = (counts[o.result] || 0) + 1
|
|
42
|
+
const reaped = outcomes
|
|
43
|
+
.filter(o => o.result === "reclaimed")
|
|
44
|
+
.map(o => o.branch || o.path.split("/").pop())
|
|
45
|
+
const line =
|
|
46
|
+
Object.entries(counts)
|
|
47
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
48
|
+
.join(", ") || "nothing to do"
|
|
49
|
+
const stamp = new Date().toISOString().slice(0, 16).replace("T", " ")
|
|
50
|
+
let text = `🧹 worktree-gc (${stamp} UTC) — ${line}`
|
|
51
|
+
if (reaped.length) text += `\nreaped: ${reaped.join(", ")}`
|
|
52
|
+
return JSON.stringify({
|
|
53
|
+
to: { channel: "telegram", address: b.input?.chatId },
|
|
54
|
+
content: { text },
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "notify",
|
|
60
|
+
kind: "tool",
|
|
61
|
+
tool: "command_execute",
|
|
62
|
+
inputs: {
|
|
63
|
+
command: "bash",
|
|
64
|
+
args: [
|
|
65
|
+
"-c",
|
|
66
|
+
[
|
|
67
|
+
"set -a",
|
|
68
|
+
". /Volumes/SSDExternalMacStudio/Code/products/agentik/agentik-studio/envs/agentpush/.env.local 2>/dev/null || true",
|
|
69
|
+
"set +a",
|
|
70
|
+
'curl -s --max-time 30 -X POST -H "Authorization: Bearer ${AGENTPUSH_API_KEY:-}" -H "Content-Type: application/json" https://api.agentpush.io/tools/send_message --data-binary @-',
|
|
71
|
+
].join("\n"),
|
|
72
|
+
],
|
|
73
|
+
stdin: "$steps.format",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}
|