@cleocode/contracts 2026.6.14 → 2026.6.17

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.
@@ -102,6 +102,10 @@ export interface WorktreeCleanupResult {
102
102
  removed: number;
103
103
  /** Paths that were removed. */
104
104
  removedPaths: string[];
105
+ /** Number of worktrees quarantined (dirty/unpushed — preserved, not deleted). */
106
+ quarantined: number;
107
+ /** Paths that were quarantined. */
108
+ quarantinedPaths: string[];
105
109
  /** Paths that failed to remove (with reasons). */
106
110
  errors: Array<{ path: string; reason: string }>;
107
111
  }
package/src/config.ts CHANGED
@@ -751,6 +751,98 @@ export interface LeadRollupConfig {
751
751
  mode?: LeadRollupMode;
752
752
  }
753
753
 
754
+ /**
755
+ * Daemon lifecycle configuration.
756
+ *
757
+ * Controls whether the postinstall hook is allowed to auto-enable and start
758
+ * the cleo-daemon service. When `autoStart` is `false`, postinstall NEVER
759
+ * enables or starts the service — even on a fresh install. This flag persists
760
+ * across upgrades so the operator's intent survives `npm install -g`.
761
+ *
762
+ * Configure via `~/.local/share/cleo/config.json` (global config):
763
+ * ```json
764
+ * { "daemon": { "autoStart": false } }
765
+ * ```
766
+ * Or via env var override: `CLEO_DAEMON_DISABLE=1` (per-session, non-persistent).
767
+ *
768
+ * @task T11984
769
+ */
770
+ export interface DaemonConfig {
771
+ /**
772
+ * Whether the postinstall hook may auto-enable and start the cleo-daemon.
773
+ *
774
+ * - `true` (default): first install enables+starts; upgrade respects the
775
+ * current systemd/launchd enabled state.
776
+ * - `false`: postinstall NEVER enables or starts the daemon. The unit file
777
+ * is still written so the operator can activate later via
778
+ * `cleo daemon enable`.
779
+ *
780
+ * @defaultValue true
781
+ */
782
+ autoStart: boolean;
783
+ }
784
+
785
+ // ---------------------------------------------------------------------------
786
+ // Resources config (T11994 · PSI + MemAvailable sensing)
787
+ // ---------------------------------------------------------------------------
788
+
789
+ /**
790
+ * PSI pressure thresholds for the ResourceMonitor.
791
+ *
792
+ * Values are percentage points (0–100) for `avg10` exponential moving averages.
793
+ * Defaults sit far below systemd-oomd's Fedora kill line (80%/20s on
794
+ * user@1000.service).
795
+ *
796
+ * @task T11994
797
+ */
798
+ export interface ResourcesPsiConfig {
799
+ /**
800
+ * Enter `hold` state when `some avg10` exceeds this value.
801
+ * @defaultValue 10
802
+ */
803
+ holdSomeAvg10?: number;
804
+ /**
805
+ * Enter `backoff` state when `some avg10` exceeds this value.
806
+ * @defaultValue 20
807
+ */
808
+ backoffSomeAvg10?: number;
809
+ /**
810
+ * Enter `hold` state when `full avg10` exceeds this value.
811
+ * @defaultValue 5
812
+ */
813
+ holdFullAvg10?: number;
814
+ /**
815
+ * Enter `backoff` state when `full avg10` exceeds this value.
816
+ * @defaultValue 10
817
+ */
818
+ backoffFullAvg10?: number;
819
+ }
820
+
821
+ /**
822
+ * Resource monitor configuration.
823
+ *
824
+ * Optional block — all fields have safe defaults. Maps to the
825
+ * `resources.*` config namespace (Amendment 5 · T11994).
826
+ *
827
+ * @task T11994
828
+ */
829
+ export interface ResourcesConfig {
830
+ /**
831
+ * PSI pressure thresholds (percentage points).
832
+ *
833
+ * @defaultValue undefined (monitor uses built-in defaults)
834
+ */
835
+ psi?: ResourcesPsiConfig;
836
+ /**
837
+ * Minimum free memory headroom in MiB.
838
+ *
839
+ * Used in degraded mode (PSI absent) as the sole availability signal.
840
+ *
841
+ * @defaultValue 256
842
+ */
843
+ headroomMb?: number;
844
+ }
845
+
754
846
  /** CLEO project configuration (config.json). */
755
847
  export interface CleoConfig {
756
848
  /** Configuration schema version string. */
@@ -838,6 +930,28 @@ export interface CleoConfig {
838
930
  * @saga T10377
839
931
  */
840
932
  leadRollup?: LeadRollupConfig;
933
+ /**
934
+ * Daemon lifecycle settings.
935
+ *
936
+ * When absent, defaults to `{ autoStart: true }` — matching the legacy
937
+ * install behaviour. Set `autoStart: false` to prevent postinstall from
938
+ * ever enabling or starting the cleo-daemon (persistent opt-out).
939
+ *
940
+ * @defaultValue undefined (treated as { autoStart: true })
941
+ * @task T11984
942
+ */
943
+ daemon?: DaemonConfig;
944
+ /**
945
+ * Resource monitor configuration — PSI pressure thresholds and memory
946
+ * headroom for the daemon-off governor and continuous supervisor mode.
947
+ *
948
+ * When absent, the ResourceMonitor uses built-in defaults which sit well
949
+ * below systemd-oomd's Fedora kill line (80%/20s on user@1000.service).
950
+ *
951
+ * @defaultValue undefined (ResourceMonitor uses built-in defaults)
952
+ * @task T11994
953
+ */
954
+ resources?: ResourcesConfig;
841
955
  }
842
956
 
843
957
  /**
@@ -9216,6 +9216,24 @@ export const OPERATIONS: OperationDef[] = [
9216
9216
  ] satisfies ParamDef[],
9217
9217
  },
9218
9218
 
9219
+ // ── selfimprove query: probe (T11988 — seeded-code-regression scenario) ────────
9220
+ // Pure read-only op that returns `{ probe: 'ok', version: probeVersion() }`.
9221
+ // The `probe-helper.ts` intentionally ships with `probeVersion()` returning `2`
9222
+ // (should be `1`) so the seeded-code-regression scenario replay diverges from its
9223
+ // golden (`version: 1`) and gives the fix-gen LLM stage a real, patchable bug.
9224
+ {
9225
+ gateway: 'query',
9226
+ domain: 'selfimprove',
9227
+ operation: 'probe',
9228
+ description:
9229
+ 'selfimprove.probe (query) — returns { probe: "ok", version: 1 } (health/version probe). Used by the seeded-code-regression scenario to validate the fix-gen pipeline (T11988).',
9230
+ tier: 2,
9231
+ idempotent: true,
9232
+ sessionRequired: false,
9233
+ requiredParams: [],
9234
+ params: [] satisfies ParamDef[],
9235
+ },
9236
+
9219
9237
  // ── selfimprove mutate: run (T11889 / T11889-D — the self-dogfooding loop) ──
9220
9238
  // Boots ONE sandbox → replays the named canned scenario → diffs result
9221
9239
  // envelopes vs the golden → on a regression emits ONE leased `selfimprove_dhq`
@@ -9863,4 +9881,31 @@ export const OPERATIONS: OperationDef[] = [
9863
9881
  },
9864
9882
  ] satisfies ParamDef[],
9865
9883
  },
9884
+ // T11995 — Janitor MVP: orphan process reaper + stale scope/lock/debris sweep
9885
+ {
9886
+ gateway: 'mutate',
9887
+ domain: 'admin',
9888
+ operation: 'janitor.run',
9889
+ description:
9890
+ 'admin.janitor.run (mutate) — run the janitor sweep: reap orphan processes, stop dead cleo-owned scopes, reclaim stale locks, prune tmp debris',
9891
+ tier: 1,
9892
+ idempotent: true,
9893
+ sessionRequired: false,
9894
+ requiredParams: [],
9895
+ params: [
9896
+ {
9897
+ name: 'dryRun',
9898
+ type: 'boolean',
9899
+ required: false,
9900
+ description: 'Report planned actions without mutating anything.',
9901
+ },
9902
+ {
9903
+ name: 'gracePeriodMs',
9904
+ type: 'number',
9905
+ required: false,
9906
+ description:
9907
+ 'Minimum age (ms) of an unregistered process before reap eligibility (default: 600000).',
9908
+ },
9909
+ ] satisfies ParamDef[],
9910
+ },
9866
9911
  ];
package/src/index.ts CHANGED
@@ -261,6 +261,7 @@ export type {
261
261
  ClaudeSpawnMode,
262
262
  CleoConfig,
263
263
  ConfigSource,
264
+ DaemonConfig,
264
265
  DateFormat,
265
266
  DecisionsConfig,
266
267
  DepsRequiredAt,
@@ -285,6 +286,8 @@ export type {
285
286
  OutputFormat,
286
287
  ProviderConfig,
287
288
  ResolvedValue,
289
+ ResourcesConfig,
290
+ ResourcesPsiConfig,
288
291
  RoleName,
289
292
  SessionConfig,
290
293
  SessionSummaryInput,
@@ -2146,7 +2149,13 @@ export type {
2146
2149
  StatsSkillLifecycleState,
2147
2150
  StatsSkillSourceType,
2148
2151
  } from './skills/stats.js';
2149
- export type { AdapterSpawnProvider, SpawnContext, SpawnResult } from './spawn.js';
2152
+ export type {
2153
+ AdapterSpawnProvider,
2154
+ AgentContainmentMode,
2155
+ AgentSuiteOwnership,
2156
+ SpawnContext,
2157
+ SpawnResult,
2158
+ } from './spawn.js';
2150
2159
  // === CLEO Spawn Types (distinct from adapter spawn) ===
2151
2160
  export type {
2152
2161
  CAAMPSpawnOptions,
@@ -255,6 +255,7 @@ export type ResolutionSource =
255
255
  | 'default'
256
256
  | 'default-profile'
257
257
  | 'registered-default'
258
+ | 'cross-provider'
258
259
  | 'implicit-fallback';
259
260
 
260
261
  /**
@@ -381,10 +381,20 @@ export interface PruneWorktreesResult {
381
381
  removed: number;
382
382
  /** Absolute paths that were removed. */
383
383
  removedPaths: string[];
384
+ /** Number of worktrees quarantined (dirty/unpushed — preserved, not deleted). */
385
+ quarantined: number;
386
+ /** Absolute paths of quarantined worktrees. */
387
+ quarantinedPaths: string[];
384
388
  /** Entries that failed to remove (with reasons). */
385
389
  errors: Array<{ path: string; reason: string }>;
386
390
  /** Whether `git worktree prune` was run. */
387
391
  gitPruneRan: boolean;
392
+ /**
393
+ * True when pruning was skipped entirely because the preserve set was empty
394
+ * or the task store was unreadable while worktrees exist (fail-closed guard,
395
+ * T11996). A structured audit warning is written to the lifecycle log.
396
+ */
397
+ skippedFailClosed?: boolean;
388
398
  }
389
399
 
390
400
  // ---------------------------------------------------------------------------
@@ -576,6 +586,7 @@ export type WorktreeLifecycleAction =
576
586
  | 'adopt'
577
587
  | 'prune'
578
588
  | 'prune-skip'
589
+ | 'quarantine'
579
590
  | 'force-unlock'
580
591
  | 'complete'
581
592
  | 'complete-skip'
package/src/spawn.ts CHANGED
@@ -4,8 +4,79 @@
4
4
  * @task T5240
5
5
  * @task T9214 — orchestrator-defer waiver field (W4 UX hardening)
6
6
  * @task T9215 — DelegateTaskEnvelope discriminated-union type (W1 wiring)
7
+ * @task T11998 — per-session suite containment (AgentSuiteOwnership)
7
8
  */
8
9
 
10
+ // =============================================================================
11
+ // Agent suite ownership handle (T11998 — suite containment)
12
+ // =============================================================================
13
+
14
+ /**
15
+ * Containment mode used when spawning an agent process tree.
16
+ *
17
+ * - `'systemd'` — the agent and its MCP children run inside a transient
18
+ * `systemd --user` scope unit. The scope is the kill handle.
19
+ * - `'pgid'` — the agent runs in its own POSIX process group. Negative-PID
20
+ * kill (`kill(-pgid, SIGTERM)`) reaps the whole group.
21
+ * - `'none'` — no containment (detached+unref legacy path). The janitor
22
+ * (T11995) is the only backstop.
23
+ */
24
+ export type AgentContainmentMode = 'systemd' | 'pgid' | 'none';
25
+
26
+ /**
27
+ * Ownership/cleanup handle recorded per spawned agent session.
28
+ *
29
+ * This record is stored alongside the {@link SpawnResult} so that
30
+ * `terminate()` and session-end flows can reap the entire process tree
31
+ * (including indirectly-spawned MCP grandchildren) via a single kill.
32
+ *
33
+ * ## Systemd path
34
+ * `systemctl --user stop <unitName>` stops the scope and all processes
35
+ * inside it. If the scope has already exited, use `reset-failed` to clear
36
+ * the failed-unit ledger; ESRCH / no-such-unit errors are no-ops.
37
+ *
38
+ * ## Pgid path
39
+ * `process.kill(-pgid, 'SIGTERM')` sends SIGTERM to every process in the
40
+ * group. After a grace period, `process.kill(-pgid, 'SIGKILL')` cleans
41
+ * up survivors. ESRCH is a no-op (group already gone).
42
+ *
43
+ * @task T11998
44
+ */
45
+ export interface AgentSuiteOwnership {
46
+ /**
47
+ * Containment mode in effect for this agent spawn.
48
+ *
49
+ * Determines which reap primitive to use on session end.
50
+ */
51
+ readonly mode: AgentContainmentMode;
52
+
53
+ /**
54
+ * Systemd transient scope unit name (e.g. `cleo-agent-session-T1234.scope`).
55
+ *
56
+ * Present only when `mode === 'systemd'`. Use as the argument to
57
+ * `systemctl --user stop <unitName>`.
58
+ */
59
+ readonly unitName?: string;
60
+
61
+ /**
62
+ * POSIX process-group ID of the spawned agent root process.
63
+ *
64
+ * Present only when `mode === 'pgid'`. Use as negative-PID with kill:
65
+ * `process.kill(-pgid, 'SIGTERM')`.
66
+ *
67
+ * When `mode === 'systemd'` the pgid is also populated as a fallback
68
+ * handle (e.g. if the scope unit is unavailable at reap time).
69
+ */
70
+ readonly pgid?: number;
71
+
72
+ /**
73
+ * PID of the direct child process spawned by cleo.
74
+ *
75
+ * Informational — prefer `unitName` or `pgid` for reaping.
76
+ */
77
+ readonly pid?: number;
78
+ }
79
+
9
80
  export interface AdapterSpawnProvider {
10
81
  canSpawn(): Promise<boolean>;
11
82
  spawn(context: SpawnContext): Promise<SpawnResult>;
@@ -48,6 +119,14 @@ export interface SpawnResult {
48
119
  endTime?: string;
49
120
  /** Error message when status is 'failed'. Contains details about what went wrong. */
50
121
  error?: string;
122
+ /**
123
+ * Suite containment ownership handle (T11998).
124
+ *
125
+ * Present when the spawn provider recorded containment info.
126
+ * Use this to reap the entire MCP suite tree on session end or terminate().
127
+ * Absent for legacy spawns that used detached+unref without containment.
128
+ */
129
+ ownership?: AgentSuiteOwnership;
51
130
  }
52
131
 
53
132
  // =============================================================================