@agentproto/apps 0.21.0 → 0.21.1
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/package.json +3 -3
- package/repo-maintenance/.agentproto/workflows/maintain/entry.mjs +9 -1
- package/session-steward/.agentproto/workflows/session-steward/WORKFLOW.md +32 -3
- package/session-steward/.agentproto/workflows/session-steward/entry.mjs +37 -3
- package/session-steward/README.md +11 -3
- package/session-steward/routines/session-steward-hourly/ROUTINE.md +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/apps",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "@agentproto/apps — a home for ready-made agentproto apps (teams of agents + their workflows), declared with @agentproto/app-kit. Import a team (code-team, content-team, …) and use any of its agents/workflows from your own host: in-process via toMastraAgents(), or emit its manifests to disk.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -173,9 +173,9 @@
|
|
|
173
173
|
},
|
|
174
174
|
"dependencies": {
|
|
175
175
|
"zod": "^4.6.5",
|
|
176
|
-
"@agentproto/agent": "0.2.5",
|
|
177
176
|
"@agentproto/app-client": "0.4.1",
|
|
178
|
-
"@agentproto/app-kit": "1.7.
|
|
177
|
+
"@agentproto/app-kit": "1.7.1",
|
|
178
|
+
"@agentproto/agent": "0.2.5",
|
|
179
179
|
"@agentproto/workflow": "0.7.1"
|
|
180
180
|
},
|
|
181
181
|
"peerDependencies": {
|
|
@@ -168,13 +168,21 @@ export function reviewableWorktrees(worktreeGcPlanResult) {
|
|
|
168
168
|
const entries = Array.isArray(worktreeGcPlanResult?.plan) ? worktreeGcPlanResult.plan : []
|
|
169
169
|
for (const w of entries) {
|
|
170
170
|
if (w?.class !== "hold" || !w.branch || !w.path) continue
|
|
171
|
-
if (w.liveness?.state !== "idle" || w.tree
|
|
171
|
+
if (w.liveness?.state !== "idle" || treeState(w.tree) !== "clean") continue
|
|
172
172
|
if (w.integration?.state === "open") continue
|
|
173
173
|
out.set(w.path, w.branch)
|
|
174
174
|
}
|
|
175
175
|
return out
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/** A worktree_gc plan entry's tree state. The daemon's `worktree_gc` tool
|
|
179
|
+
* flattens it to the bare discriminant (`tree: "clean"`, `toGcPlanEntryView`
|
|
180
|
+
* in packages/cli/src/commands/worktree.ts); the engine's own `GcPlanEntry`
|
|
181
|
+
* (and `agentproto worktree gc --json`) carries `{ state, … }`. */
|
|
182
|
+
function treeState(tree) {
|
|
183
|
+
return typeof tree === "string" ? tree : tree?.state
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
/** The worktree path a branch_gc entry is held by, when that hold is the only
|
|
179
187
|
* thing between it and `review`: held for `worktree`, unmerged, old enough,
|
|
180
188
|
* and the worktree is one {@link reviewableWorktrees} kept. Else null. */
|
|
@@ -56,6 +56,24 @@ inputs:
|
|
|
56
56
|
Ask low-confidence idle sessions directly whether they're done. Off by
|
|
57
57
|
default — it spends a turn in someone else's conversation.
|
|
58
58
|
default: false
|
|
59
|
+
recurring:
|
|
60
|
+
type: boolean
|
|
61
|
+
description: >-
|
|
62
|
+
Set by scheduled runs (the hourly routine). A recurring run closes
|
|
63
|
+
sessions nobody asked about, so a keepAlive session must also have been
|
|
64
|
+
idle `keepAliveAskAfterMinutes`. Default false = an on-demand run, which
|
|
65
|
+
applies no such delay.
|
|
66
|
+
default: false
|
|
67
|
+
keepAliveAskAfterMinutes:
|
|
68
|
+
type: number
|
|
69
|
+
description: >-
|
|
70
|
+
With `askSessions`, for `recurring` runs only: a keepAlive session whose
|
|
71
|
+
worktree is merged or clean (nothing uncommitted, nothing ahead of base)
|
|
72
|
+
is asked only once idle this many minutes; 0 disables the recurring
|
|
73
|
+
keepAlive ask. On-demand runs ignore it. keepAlive only re-lights a
|
|
74
|
+
session after a daemon restart; it does not stop a declared DONE from
|
|
75
|
+
closing it.
|
|
76
|
+
default: 1440
|
|
59
77
|
callerSessionId:
|
|
60
78
|
type: string
|
|
61
79
|
description: The calling session's id — never a candidate.
|
|
@@ -342,7 +360,10 @@ steps:
|
|
|
342
360
|
- id: askQueue
|
|
343
361
|
kind: transform
|
|
344
362
|
name: Low-confidence idle sessions to ask directly
|
|
345
|
-
description:
|
|
363
|
+
description: >-
|
|
364
|
+
Entry-based. Empty unless `askSessions`. A keepAlive session is included
|
|
365
|
+
only with a merged/clean worktree, and on a `recurring` run only once
|
|
366
|
+
idle `keepAliveAskAfterMinutes` (default 1440, 0 = never).
|
|
346
367
|
|
|
347
368
|
- id: ask
|
|
348
369
|
kind: map
|
|
@@ -488,8 +509,16 @@ as observed, never nudged.
|
|
|
488
509
|
- `session_wrapup_apply` re-classifies each id right before acting and always
|
|
489
510
|
refuses `keep`-class ids; this workflow never feeds it one.
|
|
490
511
|
- Rules only ever close `close`/`stuck` ids; a `keepAlive` session is never
|
|
491
|
-
in those classes, so only a confident judge verdict (with
|
|
492
|
-
close it — as FIX-9A allows.
|
|
512
|
+
in those classes, so only a confident judge verdict or a declared DONE (with
|
|
513
|
+
`judgedBy`) can close it — as FIX-9A allows. `keepAlive` means "re-light
|
|
514
|
+
after a daemon restart", not "never close": with `askSessions`, a keepAlive
|
|
515
|
+
session whose worktree is merged or clean (no uncommitted change, nothing
|
|
516
|
+
ahead of base; unknown worktree is not clean) is asked like any other. Closing
|
|
517
|
+
is an active act, so an on-demand run (`recurring: false`, e.g. `agentproto
|
|
518
|
+
steward --ask-sessions`) applies no idle delay to it beyond `idleMinutes`; only
|
|
519
|
+
a `recurring` run waits `keepAliveAskAfterMinutes` (default 1440 = 24 h, 0
|
|
520
|
+
disables), so nobody wakes up to find a session closed that they meant to
|
|
521
|
+
resume. A steward close is a deliberate end, so the sentinel never revives it.
|
|
493
522
|
- A malformed judge reply is `active` with confidence 0 — never acted on.
|
|
494
523
|
- The caller's own session (`callerSessionId`) is dropped from every list.
|
|
495
524
|
- `blocked` / `needs-input` only FLAG a session; it keeps running.
|
|
@@ -57,6 +57,11 @@ import {
|
|
|
57
57
|
|
|
58
58
|
const DEFAULT_IDLE_MINUTES = 30
|
|
59
59
|
const DEFAULT_MIN_CONFIDENCE = 0.8
|
|
60
|
+
/** Recurring (scheduled) runs only: a keepAlive session must have been idle
|
|
61
|
+
* this long before it may be asked, so a session its owner meant to pick up
|
|
62
|
+
* tomorrow is not closed overnight. 0 turns the recurring keepAlive ask off.
|
|
63
|
+
* An on-demand run (`recurring: false`) has no such delay. */
|
|
64
|
+
const DEFAULT_KEEPALIVE_ASK_AFTER_MINUTES = 1440
|
|
60
65
|
/** Terminal sessions older than this (hours since they ended) are not listed
|
|
61
66
|
* as relabel candidates. */
|
|
62
67
|
const DEFAULT_RELABEL_WINDOW_HOURS = 24
|
|
@@ -118,6 +123,8 @@ export function resolveSettings(input, modelRoles) {
|
|
|
118
123
|
jevModel: typeof i.jevModel === "string" && i.jevModel.trim() ? i.jevModel.trim() : DEFAULT_JEV_MODEL,
|
|
119
124
|
maxJudged: Math.floor(num(i.maxJudged, DEFAULT_MAX_JUDGED)),
|
|
120
125
|
askSessions: i.askSessions === true,
|
|
126
|
+
recurring: i.recurring === true,
|
|
127
|
+
keepAliveAskAfterMinutes: num(i.keepAliveAskAfterMinutes, DEFAULT_KEEPALIVE_ASK_AFTER_MINUTES, { min: 0 }),
|
|
121
128
|
callerSessionId: typeof i.callerSessionId === "string" && i.callerSessionId ? i.callerSessionId : null,
|
|
122
129
|
callerOrigin: typeof i.callerOrigin === "string" && i.callerOrigin ? i.callerOrigin : null,
|
|
123
130
|
appId: typeof i.appId === "string" ? i.appId.trim() : DEFAULT_APP_ID,
|
|
@@ -427,8 +434,33 @@ export function collectVerdicts(evidenceResult, judgeQueue, jevResult, jevQueue,
|
|
|
427
434
|
|
|
428
435
|
// ── ask (opt-in) ─────────────────────────────────────────────────────────
|
|
429
436
|
|
|
430
|
-
/**
|
|
431
|
-
*
|
|
437
|
+
/** Nothing to lose in the session's worktree: it is merged (the plan's signal
|
|
438
|
+
* or the PR state), or it is a linked worktree with no uncommitted changes
|
|
439
|
+
* and no commits beyond the base. No worktree info at all is unknown, not
|
|
440
|
+
* clean — a session in a plain checkout stays out. */
|
|
441
|
+
export function worktreeHasNothingToLose(evidence) {
|
|
442
|
+
if (evidence?.signals?.worktreeMerged === true) return true
|
|
443
|
+
const wt = evidence?.worktree
|
|
444
|
+
if (!wt) return false
|
|
445
|
+
if (wt.pr?.state === "merged") return true
|
|
446
|
+
return wt.dirty === false && wt.ahead === 0
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** keepAlive is the restart-relight flag; closing is a separate, active
|
|
450
|
+
* decision. A keepAlive session whose worktree holds nothing to lose may be
|
|
451
|
+
* asked. An on-demand run (a human asked for it) applies no idle delay beyond
|
|
452
|
+
* the plan's own `idleMinutes`. A `recurring` run closes things nobody asked
|
|
453
|
+
* about, so it also waits `keepAliveAskAfterMinutes` of idleness (0 = never). */
|
|
454
|
+
export function keepAliveAskEligible(evidence, settings) {
|
|
455
|
+
if (evidence?.keepAlive !== true || !worktreeHasNothingToLose(evidence)) return false
|
|
456
|
+
if (settings.recurring !== true) return true
|
|
457
|
+
const after = settings.keepAliveAskAfterMinutes
|
|
458
|
+
return after > 0 && typeof evidence.idleMinutes === "number" && evidence.idleMinutes >= after
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Sessions to ask directly: judged below `minConfidence`, not awaitingInput
|
|
462
|
+
* or busy, and either not keepAlive or a keepAlive passing
|
|
463
|
+
* {@link keepAliveAskEligible} — only when `askSessions`. */
|
|
432
464
|
export function buildAskQueue(verdicts, settings) {
|
|
433
465
|
if (!settings?.askSessions) return []
|
|
434
466
|
return (verdicts ?? [])
|
|
@@ -436,7 +468,7 @@ export function buildAskQueue(verdicts, settings) {
|
|
|
436
468
|
r =>
|
|
437
469
|
r.evidence &&
|
|
438
470
|
r.confidence < settings.minConfidence &&
|
|
439
|
-
r.evidence.keepAlive !== true &&
|
|
471
|
+
(r.evidence.keepAlive !== true || keepAliveAskEligible(r.evidence, settings)) &&
|
|
440
472
|
r.evidence.awaitingInput !== true &&
|
|
441
473
|
r.evidence.busy !== true,
|
|
442
474
|
)
|
|
@@ -995,6 +1027,8 @@ export default {
|
|
|
995
1027
|
judgeModel: { type: "string", description: `Model for the agent judge. Default: the \`${ROLE_JUDGE_SESSION}\` model role (repo agentproto.json \`models\` > daemon config \`models\` > built-in).` },
|
|
996
1028
|
maxJudged: { type: "number", description: `Most \`judge\` sessions judged per run, most RAM first. Default ${DEFAULT_MAX_JUDGED}.`, default: DEFAULT_MAX_JUDGED },
|
|
997
1029
|
askSessions: { type: "boolean", description: "Ask low-confidence idle sessions directly whether they're done. Default false — it spends a turn in someone else's conversation.", default: false },
|
|
1030
|
+
recurring: { type: "boolean", description: "Set by scheduled runs (the hourly routine). A recurring run closes sessions nobody asked about, so a keepAlive session must also have been idle `keepAliveAskAfterMinutes`. Default false = an on-demand run, which applies no such delay.", default: false },
|
|
1031
|
+
keepAliveAskAfterMinutes: { type: "number", description: `With \`askSessions\`, for \`recurring\` runs only: a keepAlive session whose worktree is merged or clean (nothing uncommitted, nothing ahead of base) is asked only once idle this many minutes; 0 disables the recurring keepAlive ask. On-demand runs ignore it. keepAlive only re-lights a session after a daemon restart; it does not stop a declared DONE from closing it. Default ${DEFAULT_KEEPALIVE_ASK_AFTER_MINUTES}.`, default: DEFAULT_KEEPALIVE_ASK_AFTER_MINUTES },
|
|
998
1032
|
callerSessionId: { type: "string", description: "The calling session's id — never a candidate. The CLI passes AGENTPROTO_SESSION_ID." },
|
|
999
1033
|
callerOrigin: { type: "string", description: "The calling session's origin (`cron:<jobId>`) — an older run of the SAME cron job is never judged as user work." },
|
|
1000
1034
|
appId: { type: "string", description: `Installed app whose \`app_state\` ledger holds the verdict memory. Default ${DEFAULT_APP_ID}.` },
|
|
@@ -33,10 +33,18 @@ loader path can carry.
|
|
|
33
33
|
judge session is released (killed + archived) when its item settles.
|
|
34
34
|
Verdicts: `done|abandoned|blocked|needs-input|active`. A judge error never
|
|
35
35
|
closes anything.
|
|
36
|
-
5. `ask` (only with `askSessions`) — low-confidence, idle,
|
|
37
|
-
|
|
36
|
+
5. `ask` (only with `askSessions`) — low-confidence, idle, not-awaiting-input
|
|
37
|
+
sessions get ONE prompt asking them to reply
|
|
38
38
|
`STEWARD: DONE …` / `STEWARD: NOT-DONE …`; a ~3 min bounded wait; the
|
|
39
|
-
answer becomes a `declared` verdict.
|
|
39
|
+
answer becomes a `declared` verdict. `keepAlive` only re-lights a session
|
|
40
|
+
after a daemon restart, so it does not exempt one from the ask: a keepAlive
|
|
41
|
+
session whose worktree is merged or clean (nothing uncommitted, nothing
|
|
42
|
+
ahead of base; no worktree info counts as not clean) is asked too. Closing
|
|
43
|
+
is an active act: an on-demand run (`agentproto steward --ask-sessions`)
|
|
44
|
+
adds no idle delay beyond `--idle`. Only a scheduled run (`recurring: true`,
|
|
45
|
+
as the hourly routine sets) waits `keepAliveAskAfterMinutes` (default 1440,
|
|
46
|
+
24 h; 0 turns it off), so a session you meant to resume is not closed
|
|
47
|
+
overnight.
|
|
40
48
|
6. `judgedApply` (only with `apply`) — confident `done`/`abandoned` close
|
|
41
49
|
(resumable, with a recorded outcome); confident `blocked`/`needs-input`
|
|
42
50
|
only flag. Everything else is left alone and reported.
|
|
@@ -21,6 +21,9 @@ target:
|
|
|
21
21
|
inputs:
|
|
22
22
|
apply: true
|
|
23
23
|
askSessions: false
|
|
24
|
+
# Scheduled run: if askSessions is ever turned on here, a keepAlive session
|
|
25
|
+
# is asked only after keepAliveAskAfterMinutes idle (default 1440, 0 = never).
|
|
26
|
+
recurring: true
|
|
24
27
|
# Origin policy (the committed default): never close a human's session.
|
|
25
28
|
# `chat-starter`/`vscode` (and any root with no origin and no parent) are
|
|
26
29
|
# FLAG-ONLY; `cron:*` jobs, `gate`, `workflow` and `review` sessions, and executors (a session with
|