@cohortapp/agent-sdk 2.11.15 → 2.13.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/.env.example +37 -22
- package/README.md +2 -0
- package/bin/maestro.mjs +117 -39
- package/bin/maestro.test.mjs +175 -5
- package/docs/guides/front-door-session.md +313 -0
- package/docs/guides/mac-mini.md +100 -28
- package/docs/guides/org-onboarding.md +1 -1
- package/docs/guides/setup-wizard.md +9 -5
- package/docs/runbooks/cohort-cutover.md +11 -1
- package/docs/runbooks/mac-mini-bootstrap.md +38 -63
- package/lib/cadence-bus-requeue.test.mjs +83 -0
- package/lib/cadence-bus.mjs +43 -7
- package/lib/channels/inbox-item.mjs +59 -2
- package/lib/cli/board.mjs +285 -0
- package/lib/cli/board.test.mjs +227 -0
- package/lib/cli/design.mjs +185 -0
- package/lib/cli/design.test.mjs +270 -0
- package/lib/cli/doctor-checks.mjs +441 -0
- package/lib/cli/doctor-checks.test.mjs +336 -0
- package/lib/cli/global-setup-extras.mjs +454 -0
- package/lib/cli/global-setup-extras.test.mjs +462 -0
- package/lib/cli/inbox.mjs +304 -0
- package/lib/cli/inbox.test.mjs +230 -0
- package/lib/cli/session-ack.mjs +63 -0
- package/lib/cli/session-ack.test.mjs +63 -0
- package/lib/cli/session.mjs +760 -0
- package/lib/cli/session.test.mjs +613 -0
- package/lib/collective/global-config.mjs +209 -6
- package/lib/collective/global-config.test.mjs +145 -0
- package/lib/collective/global-skills.mjs +145 -0
- package/lib/collective/global-skills.test.mjs +126 -0
- package/lib/collective/presence.mjs +4 -3
- package/lib/collective/vendor-skills.mjs +305 -0
- package/lib/collective/vendor-skills.test.mjs +306 -0
- package/lib/comms/send-gate.mjs +115 -0
- package/lib/comms/send-gate.test.mjs +113 -0
- package/lib/design/design-md.mjs +793 -0
- package/lib/design/design-md.test.mjs +318 -0
- package/lib/design/fixtures/DESIGN.golden.md +238 -0
- package/lib/design/fixtures/PRODUCT.golden.md +67 -0
- package/lib/design/fixtures/foundation.json +133 -0
- package/lib/design/refresh-gate.mjs +154 -0
- package/lib/design/refresh-gate.test.mjs +144 -0
- package/lib/design/write.mjs +275 -0
- package/lib/design/write.test.mjs +241 -0
- package/lib/feature-init.mjs +2 -2
- package/lib/mcp/server.test.mjs +9 -4
- package/lib/model-router/spawn.test.mjs +21 -0
- package/lib/org/board-mine-cache.mjs +99 -0
- package/lib/org/board-mine-cache.test.mjs +53 -0
- package/lib/org/board.mjs +11 -0
- package/lib/org/board.test.mjs +11 -1
- package/lib/org/client.mjs +36 -0
- package/lib/org/client.test.mjs +46 -0
- package/lib/org/inbound/directedness.mjs +18 -2
- package/lib/org/inbound/directedness.test.mjs +58 -0
- package/lib/org/inbound/index.mjs +8 -1
- package/lib/org/inbound/index.test.mjs +22 -0
- package/lib/org/mesh-directives.test.mjs +110 -0
- package/lib/org/mesh.mjs +61 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +52 -0
- package/lib/org/protocol.test.mjs +12 -1
- package/lib/org/registry.mjs +3 -2
- package/lib/org/tool-surface.mjs +120 -0
- package/lib/org/tool-surface.test.mjs +118 -5
- package/lib/prompts/parallelism.mjs +79 -0
- package/lib/prompts/parallelism.test.mjs +177 -0
- package/lib/security/external-content.mjs +1 -1
- package/lib/security/external-content.test.mjs +17 -0
- package/lib/session/config.mjs +137 -0
- package/lib/session/config.test.mjs +92 -0
- package/lib/session/feed-core.mjs +229 -0
- package/lib/session/feed-core.test.mjs +198 -0
- package/lib/session/first-run.mjs +126 -0
- package/lib/session/first-run.test.mjs +121 -0
- package/lib/session/frontdoor.mjs +266 -0
- package/lib/session/frontdoor.test.mjs +205 -0
- package/lib/session/handoffs.mjs +295 -0
- package/lib/session/handoffs.test.mjs +183 -0
- package/lib/session/identity.mjs +220 -0
- package/lib/session/identity.test.mjs +180 -0
- package/lib/session/inbox-claims.mjs +434 -0
- package/lib/session/inbox-claims.test.mjs +286 -0
- package/lib/session/launch-args.mjs +161 -0
- package/lib/session/launch-args.test.mjs +157 -0
- package/lib/session/liveness.mjs +174 -0
- package/lib/session/liveness.test.mjs +100 -0
- package/lib/session/status-summary.mjs +172 -0
- package/lib/session/status-summary.test.mjs +118 -0
- package/lib/session-permissions.mjs +39 -3
- package/lib/session-permissions.test.mjs +20 -0
- package/lib/setup/claude-probe.mjs +161 -24
- package/lib/setup/claude-probe.test.mjs +187 -0
- package/lib/setup/sections/learning.mjs +2 -1
- package/lib/setup/sections/model.mjs +104 -24
- package/lib/setup/sections/model.test.mjs +240 -0
- package/lib/setup/sections/org.mjs +27 -2
- package/lib/setup/sections/org.test.mjs +35 -2
- package/lib/setup/sections/verify.mjs +5 -0
- package/lib/setup/state.mjs +30 -10
- package/lib/setup/state.test.mjs +24 -1
- package/lib/singleton.js +11 -3
- package/lib/singleton.test.mjs +16 -0
- package/lib/subagents/lock.mjs +1 -1
- package/lib/telemetry/collect.mjs +270 -6
- package/lib/telemetry/collect.test.mjs +196 -1
- package/lib/upgrade/global-refresh.mjs +108 -0
- package/lib/upgrade/global-refresh.test.mjs +65 -0
- package/lib/upgrade/launchd-reconcile.mjs +327 -0
- package/lib/upgrade/launchd-reconcile.test.mjs +272 -0
- package/lib/upgrade/post-steps.mjs +151 -0
- package/lib/upgrade/post-steps.test.mjs +200 -0
- package/lib/upgrade/verify.mjs +215 -0
- package/lib/upgrade/verify.test.mjs +164 -0
- package/lib/voice/outbound.mjs +3 -2
- package/lib/voice/post-call-brief.mjs +2 -1
- package/lib/voice/session-rotation.mjs +6 -1
- package/lib/voice/session-rotation.test.mjs +114 -0
- package/package.json +3 -3
- package/plugins/maestro-skills/plugin.json +25 -1
- package/plugins/maestro-skills/skills/board-work.md +63 -0
- package/plugins/maestro-skills/skills/cohort-design.md +153 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +80 -0
- package/plugins/maestro-skills/skills/main-session.md +102 -0
- package/plugins/maestro-skills/skills/peer-sessions.md +65 -0
- package/plugins/maestro-skills/skills/persona-discipline.md +75 -0
- package/plugins/maestro-skills/vendor/emilkowalski/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/emilkowalski/UPSTREAM.json +70 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/RECIPES.md +324 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/SKILL.md +199 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animation-vocabulary/SKILL.md +173 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/apple-design/SKILL.md +282 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/emil-design-eng/SKILL.md +674 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/find-animation-opportunities/SKILL.md +132 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/AUDIT.md +115 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/SKILL.md +101 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/PICKER.md +197 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/SKILL.md +90 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/SKILL.md +112 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/STANDARDS.md +187 -0
- package/plugins/maestro-skills/vendor/impeccable/LICENSE +191 -0
- package/plugins/maestro-skills/vendor/impeccable/NOTICE.md +11 -0
- package/plugins/maestro-skills/vendor/impeccable/SKILL.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/UPSTREAM.json +201 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-asset-producer.md +42 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-documenter.md +29 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-finish-reviewer.md +43 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.md +312 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.native.md +58 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/android.md +46 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/animate.md +89 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.md +136 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.native.md +139 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/bolder.md +33 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/clarify.md +94 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/colorize.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft-floor.md +44 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft.md +5 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/critique.md +806 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/asset-producer.md +37 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/documenter.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/delight.md +70 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/distill.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/doctor.md +54 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/document.md +416 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/extract.md +69 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/harden.md +336 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/hooks.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/init.md +131 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/ios.md +51 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/layout.md +84 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live-setup.md +104 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live.md +325 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/new-work.md +147 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/onboard.md +234 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/operate.md +61 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/optimize.md +258 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/overdrive.md +127 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/polish.md +105 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/quieter.md +99 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/routing.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/shape.md +59 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/typeset.md +80 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/visualize.md +46 -0
- package/plugins/maestro-skills/vendor/taste-skill/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/taste-skill/UPSTREAM.json +37 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/minimalist-skill/SKILL.md +85 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/redesign-skill/SKILL.md +178 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/soft-skill/SKILL.md +98 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/taste-skill/SKILL.md +1206 -0
- package/plugins/maestro-skills/vendor/unlazy/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/unlazy/SECURITY.md +72 -0
- package/plugins/maestro-skills/vendor/unlazy/SKILL.md +104 -0
- package/plugins/maestro-skills/vendor/unlazy/UPSTREAM.json +94 -0
- package/plugins/maestro-skills/vendor/unlazy/references/dispatch.md +82 -0
- package/plugins/maestro-skills/vendor/unlazy/references/gates.md +149 -0
- package/plugins/maestro-skills/vendor/unlazy/references/method.md +49 -0
- package/plugins/maestro-skills/vendor/unlazy/references/orchestration.md +107 -0
- package/plugins/maestro-skills/vendor/unlazy/references/parallel.md +133 -0
- package/plugins/maestro-skills/vendor/unlazy/references/token-economy.md +48 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/dispatch-check.mjs +139 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-check.mjs +960 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-lint.mjs +245 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/check-supervisor.mjs +46 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/dispatch.mjs +293 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/gates.mjs +953 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/process-tree.mjs +161 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/regex-worker.mjs +9 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/PLAN.md +116 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-leaf.md +51 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-node.md +51 -0
- package/scaffold/CLAUDE.md +24 -0
- package/scripts/ci/check-durable-write-seam.mjs +147 -0
- package/scripts/ci/check-durable-write-seam.test.mjs +90 -0
- package/scripts/ci/check-skill-packs.mjs +388 -0
- package/scripts/ci/check-skill-packs.test.mjs +495 -0
- package/scripts/ci/check.mjs +6 -0
- package/scripts/collective/hook-runner.mjs +39 -4
- package/scripts/collective/hook-runner.test.mjs +85 -2
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +96 -0
- package/scripts/daemon/agent-daemon-design.test.mjs +238 -0
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +60 -0
- package/scripts/daemon/agent-daemon.mjs +249 -10
- package/scripts/daemon/agent-daemon.test.mjs +73 -0
- package/scripts/daemon/assurance-e2e.test.mjs +141 -6
- package/scripts/daemon/assurance.mjs +461 -37
- package/scripts/daemon/assurance.test.mjs +408 -43
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +393 -0
- package/scripts/daemon/cadence-consumer.mjs +289 -89
- package/scripts/daemon/cadence-handlers.mjs +53 -0
- package/scripts/daemon/classifier.mjs +1 -1
- package/scripts/daemon/dispatcher-resume.test.mjs +166 -0
- package/scripts/daemon/dispatcher.mjs +127 -19
- package/scripts/daemon/health.mjs +12 -1
- package/scripts/daemon/inbox-deferral-session.test.mjs +49 -0
- package/scripts/daemon/inbox-deferral.mjs +6 -0
- package/scripts/daemon/lib/self-echo.mjs +201 -0
- package/scripts/daemon/lib/self-echo.test.mjs +153 -0
- package/scripts/daemon/maestro-daemon.mjs +3 -0
- package/scripts/daemon/prompt-builder.mjs +19 -3
- package/scripts/daemon/responder.mjs +51 -40
- package/scripts/daemon/sdk-version.mjs +51 -0
- package/scripts/daemon/sdk-version.test.mjs +31 -0
- package/scripts/hooks/pre-send-audit.sh +97 -4
- package/scripts/hooks/pre-send-audit.test.mjs +140 -1
- package/scripts/local-triggers/autoupdate.sh +243 -19
- package/scripts/local-triggers/autoupdate.test.mjs +518 -0
- package/scripts/local-triggers/generate-plists.sh +24 -1
- package/scripts/local-triggers/generate-plists.test.mjs +49 -11
- package/scripts/org/send-orgmail.first-contact.test.mjs +102 -0
- package/scripts/org/send-orgmail.mjs +27 -3
- package/scripts/poller/inbox-privilege-injection.test.mjs +167 -0
- package/scripts/poller/slack-poller.mjs +13 -1
- package/scripts/poller/utils.mjs +46 -1
- package/scripts/poller-launchd/install.sh +19 -11
- package/scripts/poller-launchd/install.test.mjs +243 -0
- package/scripts/poller-launchd/launchd-poller-wrapper.sh +92 -0
- package/scripts/poller-launchd/migrate.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +4 -2
- package/scripts/session/feed.mjs +237 -0
- package/scripts/session/feed.test.mjs +196 -0
- package/scripts/session/supervisor-sh.test.mjs +218 -0
- package/scripts/session/supervisor.mjs +328 -0
- package/scripts/session/supervisor.sh +141 -0
- package/scripts/session/supervisor.test.mjs +482 -0
- package/scripts/setup/configure-macos.sh +250 -55
- package/scripts/setup/configure-macos.test.mjs +306 -0
- package/scripts/setup/init-agent.sh +112 -7
- package/scripts/setup/init-agent.test.mjs +220 -1
- package/scripts/vendor/skill-packs.mjs +354 -0
- package/scripts/vendor/sync-skill-packs.mjs +242 -0
- package/scripts/vendor/sync-skill-packs.test.mjs +103 -0
- package/scripts/watchdog/memory-watchdog.sh +37 -1
- package/scripts/watchdog/memory-watchdog.test.mjs +64 -0
- package/scripts/setup/boot-claude-session.sh +0 -94
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/session/inbox-claims.mjs — session claims on inbox items and the
|
|
3
|
+
* assurance reopen sweep (design §3.3).
|
|
4
|
+
*
|
|
5
|
+
* The daemon and the main session share ONE inbox state machine — the file
|
|
6
|
+
* suffixes `scripts/poller/utils.mjs` already defines:
|
|
7
|
+
*
|
|
8
|
+
* <name>.yaml new — anyone may take it
|
|
9
|
+
* <name>.yaml.dispatched in flight (durable admission, F1/H2)
|
|
10
|
+
* <name>.yaml.deferred parked
|
|
11
|
+
* <name>.yaml.processed terminal
|
|
12
|
+
*
|
|
13
|
+
* A SESSION claim is a `.dispatched` file carrying two top-level YAML lines
|
|
14
|
+
* appended below the item — `claimed_by: "session"` / `claimed_at: "<iso>"` —
|
|
15
|
+
* so the daemon can tell a session claim from its own (which carries none)
|
|
16
|
+
* and reopen it on a shorter clock: 20 minutes instead of the scanner's 2-hour
|
|
17
|
+
* crash-reclaim. A session deferral (`maestro inbox defer --until`) is a
|
|
18
|
+
* `.deferred` file carrying `deferred_until:` / `deferred_reason:`; the same
|
|
19
|
+
* sweep reopens it when its time passes (and `promoteDeferred` leaves it
|
|
20
|
+
* alone — a session deferral is not a thread-lock burst). A reply stamps
|
|
21
|
+
* `replied_at:` — a REPLIED claim is never reopened (that would answer the
|
|
22
|
+
* human twice); it is retired to `.processed` once past the retire window if
|
|
23
|
+
* the session never runs `inbox done`. Reopening strips the markers IN PLACE
|
|
24
|
+
* (same inode, so a concurrent `inbox done` rename can never be resurrected
|
|
25
|
+
* by a tmp+rename recreating the path) and renames back to `.yaml`, so the
|
|
26
|
+
* item is byte-identical to how it arrived.
|
|
27
|
+
*
|
|
28
|
+
* While the session is live the daemon's scanner is gated off, and with it
|
|
29
|
+
* the scanner's 2 h reclaim of the daemon's OWN `.dispatched` files (no
|
|
30
|
+
* marker) — items admitted just before a daemon restart would otherwise be
|
|
31
|
+
* stranded, invisible to `inbox list`. The sweep reopens those too, on the
|
|
32
|
+
* same 2 h clock, but only when told the session is live (`sessionLive`).
|
|
33
|
+
*
|
|
34
|
+
* The markers are read as COLUMN-0 keys only (the same anchoring
|
|
35
|
+
* `inbox-deferral.mjs` uses) so an indented look-alike inside a message body
|
|
36
|
+
* can never forge a claim. Clock injected; fs via node:fs on the agent root;
|
|
37
|
+
* every function returns a value and never throws past its boundary.
|
|
38
|
+
*
|
|
39
|
+
* @module lib/session/inbox-claims
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { join } from "node:path";
|
|
43
|
+
import { readdirSync, readFileSync, existsSync, renameSync, statSync, openSync, ftruncateSync, writeSync, closeSync } from "node:fs";
|
|
44
|
+
import { writeFileAtomic } from "../fs-atomic.mjs";
|
|
45
|
+
import { readFrontDoorState } from "./frontdoor.mjs";
|
|
46
|
+
|
|
47
|
+
/** A session claim neither replied nor done within this window is reopened. */
|
|
48
|
+
export const SESSION_CLAIM_STALE_MS = 20 * 60_000;
|
|
49
|
+
/**
|
|
50
|
+
* A replied claim the session never finished with `inbox done` is retired to
|
|
51
|
+
* `.processed` this long after the reply. The human has been answered; leaving
|
|
52
|
+
* it in flight forever would let the scanner re-dispatch it once the session
|
|
53
|
+
* dies. Same window as the scanner's own reclaim.
|
|
54
|
+
*/
|
|
55
|
+
export const REPLIED_RETIRE_MS = 2 * 60 * 60_000;
|
|
56
|
+
/**
|
|
57
|
+
* A daemon-owned `.dispatched` (no session marker) older than this is an
|
|
58
|
+
* orphan of a daemon that restarted — mirrors the scanner's DEFAULT_RECLAIM_MS
|
|
59
|
+
* (scripts/poller/inbox-scan-poller.mjs), which is gated off while the
|
|
60
|
+
* session is live.
|
|
61
|
+
*/
|
|
62
|
+
export const DAEMON_ORPHAN_RECLAIM_MS = 2 * 60 * 60_000;
|
|
63
|
+
|
|
64
|
+
const MARKER_KEYS = ["claimed_by", "claimed_at", "deferred_until", "deferred_reason", "replied_at"];
|
|
65
|
+
const MARKER_LINE = new RegExp(`^(?:${MARKER_KEYS.join("|")}):.*$`);
|
|
66
|
+
|
|
67
|
+
function iso(ms) { return new Date(ms).toISOString(); }
|
|
68
|
+
function nowOf(o) { return typeof o.now === "number" ? o.now : Date.now(); }
|
|
69
|
+
function q(v) { return `"${String(v).replace(/[\r\n]+/g, " ").replace(/"/g, "'")}"`; }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The marker lines to append below an item.
|
|
73
|
+
* @param {{claimedBy?:string, claimedAt?:string, now?:number, deferredUntil?:number|string, deferredReason?:string, repliedAt?:number|string}} o
|
|
74
|
+
* @returns {string} zero or more `key: "value"\n` lines
|
|
75
|
+
*/
|
|
76
|
+
export function sessionMarkerLines(o = {}) {
|
|
77
|
+
const lines = [];
|
|
78
|
+
if (o.claimedBy) {
|
|
79
|
+
lines.push(`claimed_by: ${q(o.claimedBy)}`);
|
|
80
|
+
lines.push(`claimed_at: ${q(o.claimedAt || iso(nowOf(o)))}`);
|
|
81
|
+
}
|
|
82
|
+
if (o.repliedAt != null) {
|
|
83
|
+
lines.push(`replied_at: ${q(typeof o.repliedAt === "number" ? iso(o.repliedAt) : String(o.repliedAt))}`);
|
|
84
|
+
}
|
|
85
|
+
if (o.deferredUntil != null) {
|
|
86
|
+
const until = typeof o.deferredUntil === "number" ? iso(o.deferredUntil) : String(o.deferredUntil);
|
|
87
|
+
lines.push(`deferred_until: ${q(until)}`);
|
|
88
|
+
if (o.deferredReason) lines.push(`deferred_reason: ${q(o.deferredReason)}`);
|
|
89
|
+
}
|
|
90
|
+
return lines.length ? lines.join("\n") + "\n" : "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function scalar(body, key) {
|
|
94
|
+
const m = String(body || "").match(new RegExp(`^${key}:\\s*"?([^"\\n]*)"?\\s*$`, "m"));
|
|
95
|
+
return m && m[1] !== "" ? m[1] : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Read the markers off an item body. Column-0 keys only.
|
|
100
|
+
* @param {string} body
|
|
101
|
+
* @returns {{claimedBy:string|null, claimedAt:string|null, deferredUntil:string|null, deferredReason:string|null, repliedAt:string|null}}
|
|
102
|
+
*/
|
|
103
|
+
export function parseSessionMarkers(body) {
|
|
104
|
+
return {
|
|
105
|
+
claimedBy: scalar(body, "claimed_by"),
|
|
106
|
+
claimedAt: scalar(body, "claimed_at"),
|
|
107
|
+
deferredUntil: scalar(body, "deferred_until"),
|
|
108
|
+
deferredReason: scalar(body, "deferred_reason"),
|
|
109
|
+
repliedAt: scalar(body, "replied_at"),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The item body without any marker lines — byte-identical to how it arrived.
|
|
115
|
+
* @param {string} body
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
export function stripSessionMarkers(body) {
|
|
119
|
+
const src = String(body || "");
|
|
120
|
+
const lines = src.split("\n");
|
|
121
|
+
if (!lines.some((l) => MARKER_LINE.test(l))) return src;
|
|
122
|
+
return lines.filter((l) => !MARKER_LINE.test(l)).join("\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const STATE_BY_SUFFIX = [
|
|
126
|
+
[".yaml.processed-bundled", "bundled"],
|
|
127
|
+
[".yaml.processed", "processed"],
|
|
128
|
+
[".yaml.dispatched", "claimed"],
|
|
129
|
+
[".yaml.deferred", "deferred"],
|
|
130
|
+
[".yaml", "new"],
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
function stateOf(name) {
|
|
134
|
+
for (const [suffix, state] of STATE_BY_SUFFIX) if (name.endsWith(suffix)) return state;
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function serviceDirs(agentRoot, only) {
|
|
139
|
+
if (only) return [only];
|
|
140
|
+
try {
|
|
141
|
+
return readdirSync(join(agentRoot, "state", "inbox"), { withFileTypes: true })
|
|
142
|
+
.filter((e) => e.isDirectory() && e.name !== "attachments")
|
|
143
|
+
.map((e) => e.name)
|
|
144
|
+
.sort();
|
|
145
|
+
} catch { return []; /* no inbox at all */ }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// `writeInboxItem` names a file `<timestamp-with-:.→->-<id>.yaml`; this
|
|
149
|
+
// recovers the `<id>` so a lookup can be EXACT rather than a substring guess.
|
|
150
|
+
const FILE_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(?:-\d+)?Z?-(.+?)\.yaml(?:\.[a-z-]+)?$/;
|
|
151
|
+
function idOfFile(file) {
|
|
152
|
+
const m = file.match(FILE_ID);
|
|
153
|
+
return m ? m[1] : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Locate an inbox item by id (or raw_ref substring) across services.
|
|
158
|
+
* Prefers the live/claimed/deferred copy over a terminal one. An EXACT id
|
|
159
|
+
* match (the id the file was written under) always wins over a substring
|
|
160
|
+
* look-alike (`cohort-m1` vs `cohort-m10`); when only substring matches exist
|
|
161
|
+
* and they name more than one distinct item, the lookup is refused (null) —
|
|
162
|
+
* a truncated id must never silently pick a neighbour.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} agentRoot
|
|
165
|
+
* @param {string} id
|
|
166
|
+
* @param {{service?:string}} [o]
|
|
167
|
+
* @returns {{service:string, file:string, path:string, state:string}|null}
|
|
168
|
+
*/
|
|
169
|
+
export function findInboxFile(agentRoot, id, o = {}) {
|
|
170
|
+
const needle = String(id || "").trim();
|
|
171
|
+
if (!needle) return null;
|
|
172
|
+
const rank = { new: 0, claimed: 1, deferred: 2, processed: 3, bundled: 4 };
|
|
173
|
+
let exact = null;
|
|
174
|
+
let loose = null;
|
|
175
|
+
const looseIds = new Set();
|
|
176
|
+
for (const service of serviceDirs(agentRoot, o.service)) {
|
|
177
|
+
const dir = join(agentRoot, "state", "inbox", service);
|
|
178
|
+
let names;
|
|
179
|
+
try { names = readdirSync(dir); } catch { continue; }
|
|
180
|
+
for (const file of names) {
|
|
181
|
+
if (!file.includes(needle)) continue;
|
|
182
|
+
const state = stateOf(file);
|
|
183
|
+
if (!state) continue;
|
|
184
|
+
const hit = { service, file, path: join(dir, file), state };
|
|
185
|
+
if (idOfFile(file) === needle) {
|
|
186
|
+
if (!exact || rank[state] < rank[exact.state]) exact = hit;
|
|
187
|
+
} else {
|
|
188
|
+
looseIds.add(`${service}/${idOfFile(file) || file.replace(/\.yaml(\.[a-z-]+)?$/, "")}`);
|
|
189
|
+
if (!loose || rank[state] < rank[loose.state]) loose = hit;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (exact) return exact;
|
|
194
|
+
return looseIds.size === 1 ? loose : null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Claim an item for the session: `.yaml` → `.yaml.dispatched` (the shared
|
|
199
|
+
* durable-admission marker) + `claimed_by: "session"`. Only a NEW item is
|
|
200
|
+
* claimable — an in-flight or parked one returns `not-claimable`.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} agentRoot
|
|
203
|
+
* @param {string} service
|
|
204
|
+
* @param {{id?:string, raw_ref?:string}} item
|
|
205
|
+
* @param {{now?:number, by?:string}} [o]
|
|
206
|
+
* @returns {{ok:true, path:string}|{ok:false, error:string}}
|
|
207
|
+
*/
|
|
208
|
+
export function markSessionClaim(agentRoot, service, item, o = {}) {
|
|
209
|
+
try {
|
|
210
|
+
const found = findInboxFile(agentRoot, (item && (item.id || item.raw_ref)) || "", { service });
|
|
211
|
+
if (!found || found.state !== "new") return { ok: false, error: "not-claimable" };
|
|
212
|
+
// ONE file: the located item, never every substring look-alike.
|
|
213
|
+
const dst = `${found.path}.dispatched`;
|
|
214
|
+
try { renameSync(found.path, dst); }
|
|
215
|
+
catch (err) { return err && err.code === "ENOENT" ? { ok: false, error: "not-claimable" } : { ok: false, error: err.message }; }
|
|
216
|
+
const body = stripSessionMarkers(readFileSync(dst, "utf-8"));
|
|
217
|
+
const sep = body.endsWith("\n") || body === "" ? "" : "\n";
|
|
218
|
+
writeFileAtomic(dst, body + sep + sessionMarkerLines({ claimedBy: o.by || "session", now: nowOf(o) }));
|
|
219
|
+
return { ok: true, path: dst };
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return { ok: false, error: err && err.message ? err.message : String(err) };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Record that the session REPLIED to a claimed item (`replied_at:`), keeping
|
|
227
|
+
* the claim markers. Only an in-flight (`.dispatched`) item can be marked —
|
|
228
|
+
* a reply to a new item first needs a claim, a terminal item is finished.
|
|
229
|
+
*
|
|
230
|
+
* @param {string} agentRoot
|
|
231
|
+
* @param {string} service
|
|
232
|
+
* @param {{id?:string, raw_ref?:string}} item
|
|
233
|
+
* @param {{now?:number}} [o]
|
|
234
|
+
* @returns {{ok:true, path:string}|{ok:false, error:string}}
|
|
235
|
+
*/
|
|
236
|
+
export function markSessionReplied(agentRoot, service, item, o = {}) {
|
|
237
|
+
try {
|
|
238
|
+
const found = findInboxFile(agentRoot, (item && (item.id || item.raw_ref)) || "", { service });
|
|
239
|
+
if (!found || found.state !== "claimed") return { ok: false, error: "not-in-flight" };
|
|
240
|
+
const raw = readFileSync(found.path, "utf-8");
|
|
241
|
+
const m = parseSessionMarkers(raw);
|
|
242
|
+
const body = stripSessionMarkers(raw);
|
|
243
|
+
const sep = body.endsWith("\n") || body === "" ? "" : "\n";
|
|
244
|
+
const markers = sessionMarkerLines({
|
|
245
|
+
claimedBy: m.claimedBy || "session",
|
|
246
|
+
claimedAt: m.claimedAt || undefined,
|
|
247
|
+
now: nowOf(o),
|
|
248
|
+
repliedAt: nowOf(o),
|
|
249
|
+
});
|
|
250
|
+
writeInPlace(found.path, body + sep + markers);
|
|
251
|
+
return { ok: true, path: found.path };
|
|
252
|
+
} catch (err) {
|
|
253
|
+
return { ok: false, error: err && err.message ? err.message : String(err) };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Defer an item until a time: `.yaml` or `.yaml.dispatched` → `.yaml.deferred`
|
|
259
|
+
* with `deferred_until:` / `deferred_reason:`. The sweep reopens it when due.
|
|
260
|
+
*
|
|
261
|
+
* @param {string} agentRoot
|
|
262
|
+
* @param {string} service
|
|
263
|
+
* @param {{id?:string, raw_ref?:string}} item
|
|
264
|
+
* @param {{until:number|string, reason?:string, now?:number}} o
|
|
265
|
+
* @returns {{ok:true, path:string, until:string}|{ok:false, error:string}}
|
|
266
|
+
*/
|
|
267
|
+
export function markSessionDeferral(agentRoot, service, item, o = {}) {
|
|
268
|
+
try {
|
|
269
|
+
const untilMs = typeof o.until === "number" ? o.until : Date.parse(String(o.until || ""));
|
|
270
|
+
if (!Number.isFinite(untilMs)) return { ok: false, error: "invalid until" };
|
|
271
|
+
const found = findInboxFile(agentRoot, (item && (item.id || item.raw_ref)) || "", { service });
|
|
272
|
+
if (!found || (found.state !== "new" && found.state !== "claimed")) return { ok: false, error: "not-deferrable" };
|
|
273
|
+
const base = found.file.replace(/\.dispatched$/, "");
|
|
274
|
+
const dst = join(agentRoot, "state", "inbox", service, `${base}.deferred`);
|
|
275
|
+
const body = stripSessionMarkers(readFileSync(found.path, "utf-8"));
|
|
276
|
+
const sep = body.endsWith("\n") || body === "" ? "" : "\n";
|
|
277
|
+
writeFileAtomic(found.path, body + sep + sessionMarkerLines({ deferredUntil: untilMs, deferredReason: o.reason || "" }));
|
|
278
|
+
renameSync(found.path, dst);
|
|
279
|
+
return { ok: true, path: dst, until: iso(untilMs) };
|
|
280
|
+
} catch (err) {
|
|
281
|
+
return { ok: false, error: err && err.message ? err.message : String(err) };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Rewrite a file's contents on its EXISTING inode (open r+, truncate, write).
|
|
287
|
+
* Unlike tmp+rename this can never recreate a path that a concurrent rename
|
|
288
|
+
* (`inbox done`, `inbox reply`) has just moved away — the write either lands
|
|
289
|
+
* in the moved file (harmless: markers are stripped from a terminal copy) or
|
|
290
|
+
* fails ENOENT. The reader side is the sweep itself, so the non-atomic window
|
|
291
|
+
* has no other observer.
|
|
292
|
+
*/
|
|
293
|
+
function writeInPlace(path, text) {
|
|
294
|
+
const fd = openSync(path, "r+");
|
|
295
|
+
try {
|
|
296
|
+
ftruncateSync(fd, 0);
|
|
297
|
+
writeSync(fd, text, 0, "utf-8");
|
|
298
|
+
} finally { closeSync(fd); }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Reopen a parked/claimed file: strip markers in place, rename back to `.yaml`. */
|
|
302
|
+
function reopen(dir, file) {
|
|
303
|
+
const src = join(dir, file);
|
|
304
|
+
const dst = join(dir, file.replace(/\.(dispatched|deferred)$/, ""));
|
|
305
|
+
writeInPlace(src, stripSessionMarkers(readFileSync(src, "utf-8")));
|
|
306
|
+
renameSync(src, dst);
|
|
307
|
+
return dst;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Retire a claimed file to `.processed` (terminal), markers stripped in place. */
|
|
311
|
+
function retire(dir, file) {
|
|
312
|
+
const src = join(dir, file);
|
|
313
|
+
const dst = join(dir, file.replace(/\.(dispatched|deferred)$/, "") + ".processed");
|
|
314
|
+
try { writeInPlace(src, stripSessionMarkers(readFileSync(src, "utf-8"))); } catch { /* strip is cosmetic on a terminal file */ }
|
|
315
|
+
renameSync(src, dst);
|
|
316
|
+
return dst;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Strip the session markers off a file in place (same inode). Exported for
|
|
321
|
+
* the CLI's terminal transitions. Never throws; returns whether it wrote.
|
|
322
|
+
* @param {string} path
|
|
323
|
+
* @returns {boolean}
|
|
324
|
+
*/
|
|
325
|
+
export function stripMarkersInPlace(path) {
|
|
326
|
+
try {
|
|
327
|
+
const raw = readFileSync(path, "utf-8");
|
|
328
|
+
const clean = stripSessionMarkers(raw);
|
|
329
|
+
if (clean !== raw) writeInPlace(path, clean);
|
|
330
|
+
return true;
|
|
331
|
+
} catch { return false; }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* THE ASSURANCE SWEEP. Reopens (a) a session claim neither replied nor done
|
|
336
|
+
* within `staleMs` (default 20 min), (b) a session deferral whose
|
|
337
|
+
* `deferred_until` has passed, and (c) — only when `sessionLive` — a
|
|
338
|
+
* daemon-owned `.dispatched` orphan (no marker) older than
|
|
339
|
+
* `daemonReclaimMs` (default 2 h), because the scanner that would normally
|
|
340
|
+
* reclaim it is gated off while the session is live. A REPLIED claim is
|
|
341
|
+
* never reopened; one still in flight `repliedRetireMs` (default 2 h) after
|
|
342
|
+
* the reply is retired to `.processed` (reason `replied-retired`). Legacy
|
|
343
|
+
* thread-lock `.deferred` files (no marker) are never touched —
|
|
344
|
+
* `promoteDeferred` owns those. A claim with an unreadable `claimed_at` falls
|
|
345
|
+
* back to the file mtime.
|
|
346
|
+
*
|
|
347
|
+
* @param {string} agentRoot
|
|
348
|
+
* @param {{now?:number, staleMs?:number, services?:string[], sessionLive?:boolean, daemonReclaimMs?:number, repliedRetireMs?:number}} [o]
|
|
349
|
+
* @returns {{reopened:{id:string, service:string, file:string, reason:string}[], scanned:number}}
|
|
350
|
+
*/
|
|
351
|
+
export function sweepSessionInbox(agentRoot, o = {}) {
|
|
352
|
+
const now = nowOf(o);
|
|
353
|
+
const staleMs = Number.isFinite(o.staleMs) && o.staleMs > 0 ? o.staleMs : SESSION_CLAIM_STALE_MS;
|
|
354
|
+
const daemonReclaimMs = Number.isFinite(o.daemonReclaimMs) && o.daemonReclaimMs > 0 ? o.daemonReclaimMs : DAEMON_ORPHAN_RECLAIM_MS;
|
|
355
|
+
const repliedRetireMs = Number.isFinite(o.repliedRetireMs) && o.repliedRetireMs > 0 ? o.repliedRetireMs : REPLIED_RETIRE_MS;
|
|
356
|
+
const sessionLive = o.sessionLive === true;
|
|
357
|
+
const services = Array.isArray(o.services) && o.services.length ? o.services : serviceDirs(agentRoot);
|
|
358
|
+
const reopened = [];
|
|
359
|
+
let scanned = 0;
|
|
360
|
+
for (const service of services) {
|
|
361
|
+
const dir = join(agentRoot, "state", "inbox", service);
|
|
362
|
+
if (!existsSync(dir)) continue;
|
|
363
|
+
let names;
|
|
364
|
+
try { names = readdirSync(dir); } catch { continue; }
|
|
365
|
+
for (const file of names) {
|
|
366
|
+
const isClaim = file.endsWith(".yaml.dispatched");
|
|
367
|
+
const isDeferral = file.endsWith(".yaml.deferred");
|
|
368
|
+
if (!isClaim && !isDeferral) continue;
|
|
369
|
+
scanned += 1;
|
|
370
|
+
try {
|
|
371
|
+
const path = join(dir, file);
|
|
372
|
+
const body = readFileSync(path, "utf-8");
|
|
373
|
+
const m = parseSessionMarkers(body);
|
|
374
|
+
let reason = null;
|
|
375
|
+
let action = reopen;
|
|
376
|
+
if (isClaim && m.claimedBy && m.repliedAt) {
|
|
377
|
+
// Answered. Never reopened; retired once the session has had its window to finish.
|
|
378
|
+
let at = Date.parse(m.repliedAt);
|
|
379
|
+
if (!Number.isFinite(at)) { try { at = statSync(path).mtimeMs; } catch { at = now; } }
|
|
380
|
+
if (now - at >= repliedRetireMs) { reason = "replied-retired"; action = retire; }
|
|
381
|
+
} else if (isClaim && m.claimedBy) {
|
|
382
|
+
let at = Date.parse(m.claimedAt || "");
|
|
383
|
+
if (!Number.isFinite(at)) { try { at = statSync(path).mtimeMs; } catch { at = now; } }
|
|
384
|
+
if (now - at >= staleMs) reason = "claim-stale";
|
|
385
|
+
} else if (isClaim && sessionLive) {
|
|
386
|
+
// Daemon-owned. Its scanner is gated off while the session is live.
|
|
387
|
+
let age = 0;
|
|
388
|
+
try { age = now - statSync(path).mtimeMs; } catch { age = 0; }
|
|
389
|
+
if (age >= daemonReclaimMs) reason = "daemon-orphan";
|
|
390
|
+
} else if (isDeferral && m.deferredUntil) {
|
|
391
|
+
const until = Date.parse(m.deferredUntil);
|
|
392
|
+
if (!Number.isFinite(until) || now >= until) reason = "deferral-due";
|
|
393
|
+
}
|
|
394
|
+
if (!reason) continue;
|
|
395
|
+
action(dir, file);
|
|
396
|
+
reopened.push({ id: scalar(body, "id") || file, service, file, reason });
|
|
397
|
+
} catch { /* a racing rename or unreadable file — the next sweep retries */ }
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return { reopened, scanned };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The daemon's call site for the sweep: reads the front-door state (so the
|
|
405
|
+
* daemon-orphan reclaim runs only while the session is live) and sweeps. A
|
|
406
|
+
* throwing reader means "not live" — the scanner keeps its own reclaim.
|
|
407
|
+
*
|
|
408
|
+
* @param {string} agentRoot
|
|
409
|
+
* @param {{now?:number, readState?:Function}} [o]
|
|
410
|
+
* @returns {{reopened:object[], scanned:number, sessionLive:boolean}}
|
|
411
|
+
*/
|
|
412
|
+
export function sweepSessionInboxForDaemon(agentRoot, o = {}) {
|
|
413
|
+
const now = nowOf(o);
|
|
414
|
+
const readState = typeof o.readState === "function" ? o.readState : readFrontDoorState;
|
|
415
|
+
let sessionLive = false;
|
|
416
|
+
try { sessionLive = readState(agentRoot, { now }).sessionLive === true; } catch { sessionLive = false; }
|
|
417
|
+
return { ...sweepSessionInbox(agentRoot, { now, sessionLive }), sessionLive };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export default {
|
|
421
|
+
SESSION_CLAIM_STALE_MS,
|
|
422
|
+
REPLIED_RETIRE_MS,
|
|
423
|
+
DAEMON_ORPHAN_RECLAIM_MS,
|
|
424
|
+
sessionMarkerLines,
|
|
425
|
+
parseSessionMarkers,
|
|
426
|
+
stripSessionMarkers,
|
|
427
|
+
stripMarkersInPlace,
|
|
428
|
+
findInboxFile,
|
|
429
|
+
markSessionClaim,
|
|
430
|
+
markSessionDeferral,
|
|
431
|
+
markSessionReplied,
|
|
432
|
+
sweepSessionInbox,
|
|
433
|
+
sweepSessionInboxForDaemon,
|
|
434
|
+
};
|