@aitne/daemon 0.1.9 → 0.1.10

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.
Files changed (65) hide show
  1. package/dist/api/env-writer.d.ts +1 -0
  2. package/dist/api/env-writer.js +9 -2
  3. package/dist/api/routes/agent-schedule.js +5 -1
  4. package/dist/api/routes/apple-calendar.js +4 -1
  5. package/dist/api/routes/calendar.js +12 -2
  6. package/dist/api/routes/context/path-resolve.js +6 -1
  7. package/dist/api/routes/context/permissions.js +9 -0
  8. package/dist/api/routes/dashboard/config.js +10 -0
  9. package/dist/api/routes/dashboard/oauth-google.js +5 -3
  10. package/dist/api/routes/feedback.d.ts +3 -0
  11. package/dist/api/routes/feedback.js +349 -0
  12. package/dist/api/routes/git.js +10 -3
  13. package/dist/api/routes/github.js +5 -1
  14. package/dist/api/routes/mcp.js +65 -13
  15. package/dist/api/server.js +3 -0
  16. package/dist/bootstrap/event-pipeline.js +1 -1
  17. package/dist/config.js +6 -0
  18. package/dist/core/backends/gemini-cli-core.js +13 -0
  19. package/dist/core/backends/plan-presets.js +8 -3
  20. package/dist/core/context-builder.js +149 -3
  21. package/dist/core/context-paths.d.ts +10 -0
  22. package/dist/core/context-paths.js +16 -0
  23. package/dist/core/daemon-api-cli.js +1 -1
  24. package/dist/core/dispatcher-message-handler.js +7 -0
  25. package/dist/core/dispatcher-scheduled-tasks.d.ts +41 -0
  26. package/dist/core/dispatcher-scheduled-tasks.js +267 -2
  27. package/dist/core/dispatcher.js +13 -1
  28. package/dist/core/feedback/consolidation-prep.d.ts +94 -0
  29. package/dist/core/feedback/consolidation-prep.js +242 -0
  30. package/dist/core/feedback/eviction-scorer.d.ts +81 -0
  31. package/dist/core/feedback/eviction-scorer.js +132 -0
  32. package/dist/core/feedback/lesson-format.d.ts +79 -0
  33. package/dist/core/feedback/lesson-format.js +194 -0
  34. package/dist/core/feedback/lesson-injection.d.ts +98 -0
  35. package/dist/core/feedback/lesson-injection.js +159 -0
  36. package/dist/core/feedback/lesson-merge.d.ts +51 -0
  37. package/dist/core/feedback/lesson-merge.js +88 -0
  38. package/dist/core/feedback/lesson-store-overview.d.ts +42 -0
  39. package/dist/core/feedback/lesson-store-overview.js +38 -0
  40. package/dist/core/feedback/promotion-gate.d.ts +69 -0
  41. package/dist/core/feedback/promotion-gate.js +117 -0
  42. package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
  43. package/dist/core/feedback/regeneralization-prep.js +139 -0
  44. package/dist/core/feedback/scope-parser.d.ts +86 -0
  45. package/dist/core/feedback/scope-parser.js +141 -0
  46. package/dist/core/injection-policy.d.ts +82 -0
  47. package/dist/core/injection-policy.js +58 -0
  48. package/dist/core/signal-detector.d.ts +39 -1
  49. package/dist/core/signal-detector.js +277 -24
  50. package/dist/core/today-direct-writer.d.ts +59 -13
  51. package/dist/core/today-direct-writer.js +90 -13
  52. package/dist/core/wiki/wiki-fts.js +13 -6
  53. package/dist/db/feedback-signals-store.d.ts +77 -0
  54. package/dist/db/feedback-signals-store.js +144 -0
  55. package/dist/db/migrations.js +50 -0
  56. package/dist/db/schema.js +43 -6
  57. package/dist/safety/always-disallowed.d.ts +1 -1
  58. package/dist/safety/always-disallowed.js +39 -0
  59. package/dist/safety/risk-classifier.js +22 -7
  60. package/dist/services/browser-history/automation/egress-denylist.js +18 -2
  61. package/dist/services/browser-history/lifecycle/platform.js +44 -2
  62. package/dist/services/mcp/probe.js +30 -8
  63. package/dist/settings/runtime-settings.d.ts +8 -2
  64. package/dist/settings/runtime-settings.js +12 -0
  65. package/package.json +2 -2
@@ -1,9 +1,15 @@
1
1
  /**
2
- * Daemon-direct writer that appends a single bullet to today.md
3
- * `## Agent Log` without going through the agent at all. Used by the
4
- * three-stage hourly_check gate (cost-reduction-structural §B) on the
5
- * stage0_silent / stage2_log_only paths so a "no-op" cron tick still
6
- * leaves an audit trail in today.md without paying for an LLM session.
2
+ * Daemon-direct, lock-aware writes to today.md that run *before* (or
3
+ * instead of) an agent session bypassing `/api/context/*` because there
4
+ * is no subprocess to issue the curl call from. Two operations live here:
5
+ *
6
+ * 1. {@link appendAgentLogLine} append a single `## Agent Log` bullet.
7
+ * Used by the three-stage hourly_check gate (cost-reduction-structural
8
+ * §B) on the stage0_silent / stage2_log_only paths so a "no-op" cron
9
+ * tick still leaves an audit trail without paying for an LLM session.
10
+ * 2. {@link ensureTodaySkeleton} — seed the canonical empty skeleton when
11
+ * today.md is **absent**, so a section-only refresh routine has a valid
12
+ * PATCH target instead of 404-ing and budget-burning on full-file PUTs.
7
13
  *
8
14
  * Why this lives in the daemon (not /api/context/* via curl):
9
15
  * - These paths run *before* the agent is spawned. There is no
@@ -12,15 +18,16 @@
12
18
  * `context-staleness.ts`) — the same tier the PATCH route already
13
19
  * classifies for `## Agent Log` appends. Bypassing the route is
14
20
  * fine because we never touch the prompt-context-changed hook here.
15
- * - The today-write-lock invariant is preserved: we acquire it before
16
- * mutating the file, so morning_routine and direct writes never
17
- * interleave.
21
+ * - The today-write-lock invariant is preserved: both functions acquire
22
+ * it before mutating the file, so morning_routine and direct writes
23
+ * never interleave.
18
24
  *
19
- * Failure mode: when today.md is missing, malformed, or lacks the
20
- * `## Agent Log` heading, the writer logs a warning and returns false
21
- * rather than synthesizing the structure — that is the morning routine's
22
- * job. The gate caller treats false as "log not appended; consume
23
- * observations regardless".
25
+ * Synthesis boundary: `appendAgentLogLine` NEVER synthesizes structure
26
+ * a missing / malformed / heading-less file returns false and the gate
27
+ * caller proceeds. `ensureTodaySkeleton` synthesizes ONLY the empty
28
+ * skeleton, ONLY when the file is entirely absent, and never touches a
29
+ * present file. Neither populates today.md — full creation and repair stay
30
+ * the morning routine's job.
24
31
  */
25
32
  import type { TodayWriteLockManager } from "./today-write-lock.js";
26
33
  export interface AppendAgentLogLineInput {
@@ -61,6 +68,45 @@ export interface AppendAgentLogLineResult {
61
68
  * writers cleanly.
62
69
  */
63
70
  export declare function appendAgentLogLine(input: AppendAgentLogLineInput): Promise<AppendAgentLogLineResult>;
71
+ export interface EnsureTodaySkeletonInput {
72
+ contextDir: string;
73
+ todayWriteLock: TodayWriteLockManager;
74
+ }
75
+ export interface EnsureTodaySkeletonResult {
76
+ seeded: boolean;
77
+ reason?: "already_present" | "lock_unavailable" | "io_error";
78
+ }
79
+ /**
80
+ * Guarantee a `today.md` working surface exists before a section-only
81
+ * refresh routine (`routine.today_refresh`) assumes it.
82
+ *
83
+ * `rotateDayFiles()` intentionally renames `today.md` → `yesterday.md` at
84
+ * the day boundary and relies on the morning routine to recreate the
85
+ * dated file. When the morning routine has not run yet — or failed (e.g.
86
+ * a quota/budget death with no fallback backend) — `today.md` is absent
87
+ * and the refresh task flow's `PATCH section=user_schedule` 404s. The
88
+ * agent then improvises full-file `PUT`s, which the strict
89
+ * `validateTodayContent` schema rejects line-by-line; on a single-backend
90
+ * binding with a tight per-turn budget that loop tips into
91
+ * `BackendQuotaError(max_budget_usd)` and the refresh dies without ever
92
+ * writing the file — the "Refresh Today does nothing" symptom.
93
+ *
94
+ * This deterministic pre-step removes that whole failure mode: when the
95
+ * file is **entirely absent** we seed the canonical empty skeleton so the
96
+ * agent's section PATCH always has a valid target. A file that already
97
+ * exists is left byte-untouched — a valid dated file OR the legacy
98
+ * `# Today` bridge stub both accept the section PATCH (the route's
99
+ * `allowLegacyToday` branch). We never repair a malformed-but-present
100
+ * file and never overwrite user content; full creation/repair stays the
101
+ * morning routine's job. The seeded skeleton is dateless (`# Today`), so
102
+ * it does NOT satisfy `hasCurrentAgentDayTodayMd()` and the pending
103
+ * morning-routine retry still fires and upgrades it.
104
+ *
105
+ * Lock-aware exactly like {@link appendAgentLogLine}: if the morning
106
+ * routine holds the today-write-lock (mid-creation) we skip and let it
107
+ * win — the refresh session then 409-defers on its own PATCH.
108
+ */
109
+ export declare function ensureTodaySkeleton(input: EnsureTodaySkeletonInput): Promise<EnsureTodaySkeletonResult>;
64
110
  /**
65
111
  * Splice a new bullet line into the `## Agent Log` section, immediately
66
112
  * before the next `## ` heading or end-of-file. Returns null when the
@@ -1,9 +1,15 @@
1
1
  /**
2
- * Daemon-direct writer that appends a single bullet to today.md
3
- * `## Agent Log` without going through the agent at all. Used by the
4
- * three-stage hourly_check gate (cost-reduction-structural §B) on the
5
- * stage0_silent / stage2_log_only paths so a "no-op" cron tick still
6
- * leaves an audit trail in today.md without paying for an LLM session.
2
+ * Daemon-direct, lock-aware writes to today.md that run *before* (or
3
+ * instead of) an agent session bypassing `/api/context/*` because there
4
+ * is no subprocess to issue the curl call from. Two operations live here:
5
+ *
6
+ * 1. {@link appendAgentLogLine} append a single `## Agent Log` bullet.
7
+ * Used by the three-stage hourly_check gate (cost-reduction-structural
8
+ * §B) on the stage0_silent / stage2_log_only paths so a "no-op" cron
9
+ * tick still leaves an audit trail without paying for an LLM session.
10
+ * 2. {@link ensureTodaySkeleton} — seed the canonical empty skeleton when
11
+ * today.md is **absent**, so a section-only refresh routine has a valid
12
+ * PATCH target instead of 404-ing and budget-burning on full-file PUTs.
7
13
  *
8
14
  * Why this lives in the daemon (not /api/context/* via curl):
9
15
  * - These paths run *before* the agent is spawned. There is no
@@ -12,20 +18,22 @@
12
18
  * `context-staleness.ts`) — the same tier the PATCH route already
13
19
  * classifies for `## Agent Log` appends. Bypassing the route is
14
20
  * fine because we never touch the prompt-context-changed hook here.
15
- * - The today-write-lock invariant is preserved: we acquire it before
16
- * mutating the file, so morning_routine and direct writes never
17
- * interleave.
21
+ * - The today-write-lock invariant is preserved: both functions acquire
22
+ * it before mutating the file, so morning_routine and direct writes
23
+ * never interleave.
18
24
  *
19
- * Failure mode: when today.md is missing, malformed, or lacks the
20
- * `## Agent Log` heading, the writer logs a warning and returns false
21
- * rather than synthesizing the structure — that is the morning routine's
22
- * job. The gate caller treats false as "log not appended; consume
23
- * observations regardless".
25
+ * Synthesis boundary: `appendAgentLogLine` NEVER synthesizes structure
26
+ * a missing / malformed / heading-less file returns false and the gate
27
+ * caller proceeds. `ensureTodaySkeleton` synthesizes ONLY the empty
28
+ * skeleton, ONLY when the file is entirely absent, and never touches a
29
+ * present file. Neither populates today.md — full creation and repair stay
30
+ * the morning routine's job.
24
31
  */
25
32
  import { existsSync, readFileSync } from "node:fs";
26
33
  import { writeFileAtomically } from "./atomic-write.js";
27
34
  import { serializeContextFileWrite } from "./context-file-serializer.js";
28
35
  import { fullPath, CONTEXT_RELATIVE_PATHS } from "./context-paths.js";
36
+ import { FALLBACK_PLACEHOLDERS } from "./skeleton.js";
29
37
  import { createLogger } from "../logging.js";
30
38
  const logger = createLogger("today-direct-writer");
31
39
  const AGENT_LOG_HEADER = "## Agent Log";
@@ -87,6 +95,75 @@ export async function appendAgentLogLine(input) {
87
95
  input.todayWriteLock.release(lock.lockId);
88
96
  }
89
97
  }
98
+ /**
99
+ * Canonical empty `today.md` skeleton, reused byte-for-byte from the
100
+ * boot-time seeder (`skeleton.ts`) so a refresh-path seed and a
101
+ * fresh-install seed produce identical structure. `skeleton.test.ts`
102
+ * asserts this placeholder matches `agent-assets/templates/state/today.md`
103
+ * byte-for-byte, so the two definitions never drift. The non-null
104
+ * assertion is safe: the key is a literal entry of `FALLBACK_PLACEHOLDERS`.
105
+ */
106
+ const TODAY_SKELETON = FALLBACK_PLACEHOLDERS[CONTEXT_RELATIVE_PATHS.today];
107
+ /**
108
+ * Guarantee a `today.md` working surface exists before a section-only
109
+ * refresh routine (`routine.today_refresh`) assumes it.
110
+ *
111
+ * `rotateDayFiles()` intentionally renames `today.md` → `yesterday.md` at
112
+ * the day boundary and relies on the morning routine to recreate the
113
+ * dated file. When the morning routine has not run yet — or failed (e.g.
114
+ * a quota/budget death with no fallback backend) — `today.md` is absent
115
+ * and the refresh task flow's `PATCH section=user_schedule` 404s. The
116
+ * agent then improvises full-file `PUT`s, which the strict
117
+ * `validateTodayContent` schema rejects line-by-line; on a single-backend
118
+ * binding with a tight per-turn budget that loop tips into
119
+ * `BackendQuotaError(max_budget_usd)` and the refresh dies without ever
120
+ * writing the file — the "Refresh Today does nothing" symptom.
121
+ *
122
+ * This deterministic pre-step removes that whole failure mode: when the
123
+ * file is **entirely absent** we seed the canonical empty skeleton so the
124
+ * agent's section PATCH always has a valid target. A file that already
125
+ * exists is left byte-untouched — a valid dated file OR the legacy
126
+ * `# Today` bridge stub both accept the section PATCH (the route's
127
+ * `allowLegacyToday` branch). We never repair a malformed-but-present
128
+ * file and never overwrite user content; full creation/repair stays the
129
+ * morning routine's job. The seeded skeleton is dateless (`# Today`), so
130
+ * it does NOT satisfy `hasCurrentAgentDayTodayMd()` and the pending
131
+ * morning-routine retry still fires and upgrades it.
132
+ *
133
+ * Lock-aware exactly like {@link appendAgentLogLine}: if the morning
134
+ * routine holds the today-write-lock (mid-creation) we skip and let it
135
+ * win — the refresh session then 409-defers on its own PATCH.
136
+ */
137
+ export async function ensureTodaySkeleton(input) {
138
+ const lock = input.todayWriteLock.acquire();
139
+ if (!lock.ok) {
140
+ logger.info({ holder: lock.holder }, "Skipping today.md skeleton seed — today-write-lock held");
141
+ return { seeded: false, reason: "lock_unavailable" };
142
+ }
143
+ try {
144
+ const path = fullPath(input.contextDir, CONTEXT_RELATIVE_PATHS.today);
145
+ return await serializeContextFileWrite(path, () => {
146
+ if (existsSync(path)) {
147
+ return {
148
+ seeded: false,
149
+ reason: "already_present",
150
+ };
151
+ }
152
+ try {
153
+ writeFileAtomically(path, TODAY_SKELETON);
154
+ logger.info({ path }, "Seeded today.md skeleton for refresh — file was absent (morning routine not yet run for the agent-day)");
155
+ return { seeded: true };
156
+ }
157
+ catch (err) {
158
+ logger.error({ err, path }, "Failed to seed today.md skeleton");
159
+ return { seeded: false, reason: "io_error" };
160
+ }
161
+ });
162
+ }
163
+ finally {
164
+ input.todayWriteLock.release(lock.lockId);
165
+ }
166
+ }
90
167
  /**
91
168
  * Splice a new bullet line into the `## Agent Log` section, immediately
92
169
  * before the next `## ` heading or end-of-file. Returns null when the
@@ -110,15 +110,22 @@ function extractTitleAndBody(content) {
110
110
  return { title, body };
111
111
  }
112
112
  function stripFrontmatter(content) {
113
- if (!content.startsWith("---\n")) {
114
- return { frontmatterKeys: {}, body: content };
113
+ // Obsidian vaults authored/synced on Windows (or checked out under
114
+ // git core.autocrlf=true) are CRLF. The frontmatter fence gate below is
115
+ // LF-only, so without this normalize a `---\r\n…---\r\n` block leaks into
116
+ // the indexed body and the title fallback never populates. Indexed
117
+ // body/title are search tokens only and never round-trip to disk, so
118
+ // collapsing interior CRLF to LF is harmless (LF input is unchanged).
119
+ const normalized = content.replace(/\r\n/g, "\n");
120
+ if (!normalized.startsWith("---\n")) {
121
+ return { frontmatterKeys: {}, body: normalized };
115
122
  }
116
- const end = content.indexOf("\n---\n", 4);
123
+ const end = normalized.indexOf("\n---\n", 4);
117
124
  if (end < 0) {
118
- return { frontmatterKeys: {}, body: content };
125
+ return { frontmatterKeys: {}, body: normalized };
119
126
  }
120
- const frontmatter = content.slice(4, end);
121
- const body = content.slice(end + 5);
127
+ const frontmatter = normalized.slice(4, end);
128
+ const body = normalized.slice(end + 5);
122
129
  const keys = {};
123
130
  for (const line of frontmatter.split("\n")) {
124
131
  const match = line.match(/^title:\s*(.*?)\s*$/);
@@ -0,0 +1,77 @@
1
+ import type Database from "better-sqlite3";
2
+ export type FeedbackSignalSource = "behavioral" | "explicit" | "self_critique";
3
+ export type FeedbackSignalValence = "positive" | "negative" | "neutral" | "correction";
4
+ export type FeedbackScopeType = "user" | "agent" | "agent_slug" | "channel" | "task" | "integration";
5
+ export type FeedbackActionKind = "notification" | "agent_execution" | "vault_write" | "dm_reply";
6
+ export interface FeedbackSignalRow {
7
+ id: number;
8
+ created_at: string;
9
+ source: FeedbackSignalSource;
10
+ valence: FeedbackSignalValence | null;
11
+ scope_type: FeedbackScopeType;
12
+ scope_ref: string | null;
13
+ action_kind: FeedbackActionKind | null;
14
+ action_ref: string | null;
15
+ agent_id: string | null;
16
+ summary: string;
17
+ evidence_json: string | null;
18
+ consumed_at: string | null;
19
+ lesson_ref: string | null;
20
+ }
21
+ export interface RecordFeedbackSignalParams {
22
+ source: FeedbackSignalSource;
23
+ valence?: FeedbackSignalValence | null;
24
+ scopeType: FeedbackScopeType;
25
+ scopeRef?: string | null;
26
+ actionKind?: FeedbackActionKind | null;
27
+ actionRef?: string | null;
28
+ agentId?: string | null;
29
+ summary: string;
30
+ evidence?: unknown;
31
+ }
32
+ export interface RecentFeedbackSignalLookup {
33
+ scopeType: FeedbackScopeType;
34
+ scopeRef?: string | null;
35
+ summary: string;
36
+ withinSeconds: number;
37
+ }
38
+ export declare function recordFeedbackSignal(db: Database.Database, params: RecordFeedbackSignalParams): number;
39
+ export declare function findRecentFeedbackSignal(db: Database.Database, params: RecentFeedbackSignalLookup): FeedbackSignalRow | null;
40
+ export declare function hasFeedbackSignalForAction(db: Database.Database, params: {
41
+ source: FeedbackSignalSource;
42
+ actionKind: FeedbackActionKind;
43
+ actionRef: string;
44
+ valence?: FeedbackSignalValence | null;
45
+ userReaction?: string;
46
+ }): boolean;
47
+ export declare function getPendingFeedbackSignals(db: Database.Database, params?: {
48
+ scopeType?: FeedbackScopeType;
49
+ scopeRef?: string | null;
50
+ limit?: number;
51
+ offset?: number;
52
+ }): FeedbackSignalRow[];
53
+ /**
54
+ * Count unconsumed signals, optionally narrowed to one scope type. Drives the
55
+ * `GET /api/feedback/lessons` "N signals awaiting tonight's consolidation"
56
+ * health figure (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5) without loading
57
+ * the rows. Uses the same `consumed_at IS NULL` partial index as
58
+ * {@link getPendingFeedbackSignals}.
59
+ */
60
+ export declare function countPendingFeedbackSignals(db: Database.Database, params?: {
61
+ scopeType?: FeedbackScopeType;
62
+ }): number;
63
+ export declare function consumeFeedbackSignals(db: Database.Database, ids: number[], lessonRef?: string | null): {
64
+ consumed: number;
65
+ notFound: number[];
66
+ };
67
+ export declare function sweepConsumedFeedbackSignals(db: Database.Database, cutoff: string): number;
68
+ /**
69
+ * Compute the retention cutoff ISO timestamp for
70
+ * {@link sweepConsumedFeedbackSignals} from the `feedbackSignalRetentionDays`
71
+ * knob. Returns `null` when the knob is missing or non-finite (NaN / Infinity),
72
+ * so the caller degrades to "skip the sweep" instead of throwing on
73
+ * `new Date(NaN).toISOString()` and abandoning the whole nightly consolidation
74
+ * (FEEDBACK_LEARNING_LOOP_DESIGN.md §11 v1.3 robustness). `nowMs` is injected so
75
+ * the math is deterministically testable.
76
+ */
77
+ export declare function feedbackRetentionCutoff(retentionDays: number | undefined, nowMs: number): string | null;
@@ -0,0 +1,144 @@
1
+ export function recordFeedbackSignal(db, params) {
2
+ const evidenceJson = params.evidence === undefined ? "{}" : JSON.stringify(params.evidence);
3
+ const row = db
4
+ .prepare(`INSERT INTO feedback_signals (
5
+ source,
6
+ valence,
7
+ scope_type,
8
+ scope_ref,
9
+ action_kind,
10
+ action_ref,
11
+ agent_id,
12
+ summary,
13
+ evidence_json
14
+ )
15
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
16
+ RETURNING id`)
17
+ .get(params.source, params.valence ?? null, params.scopeType, params.scopeRef ?? null, params.actionKind ?? null, params.actionRef ?? null, params.agentId ?? null, params.summary, evidenceJson);
18
+ return row.id;
19
+ }
20
+ export function findRecentFeedbackSignal(db, params) {
21
+ const row = db
22
+ .prepare(`SELECT *
23
+ FROM feedback_signals
24
+ WHERE scope_type = ?
25
+ AND COALESCE(scope_ref, '') = COALESCE(?, '')
26
+ AND summary = ?
27
+ AND datetime(created_at) >= datetime('now', '-' || ? || ' seconds')
28
+ ORDER BY datetime(created_at) DESC, id DESC
29
+ LIMIT 1`)
30
+ .get(params.scopeType, params.scopeRef ?? null, params.summary, Math.max(0, Math.floor(params.withinSeconds)));
31
+ return row ?? null;
32
+ }
33
+ export function hasFeedbackSignalForAction(db, params) {
34
+ const where = [
35
+ "source = ?",
36
+ "action_kind = ?",
37
+ "action_ref = ?",
38
+ ];
39
+ const values = [params.source, params.actionKind, params.actionRef];
40
+ if (params.valence !== undefined) {
41
+ where.push(params.valence === null ? "valence IS NULL" : "valence = ?");
42
+ if (params.valence !== null)
43
+ values.push(params.valence);
44
+ }
45
+ if (params.userReaction !== undefined) {
46
+ where.push("json_extract(evidence_json, '$.userReaction') = ?");
47
+ values.push(params.userReaction);
48
+ }
49
+ const row = db
50
+ .prepare(`SELECT 1 AS present
51
+ FROM feedback_signals
52
+ WHERE ${where.join(" AND ")}
53
+ LIMIT 1`)
54
+ .get(...values);
55
+ return row !== undefined;
56
+ }
57
+ export function getPendingFeedbackSignals(db, params = {}) {
58
+ const where = ["consumed_at IS NULL"];
59
+ const values = [];
60
+ if (params.scopeType !== undefined) {
61
+ where.push("scope_type = ?");
62
+ values.push(params.scopeType);
63
+ }
64
+ if (params.scopeRef !== undefined) {
65
+ where.push("COALESCE(scope_ref, '') = COALESCE(?, '')");
66
+ values.push(params.scopeRef);
67
+ }
68
+ const limit = Math.min(Math.max(params.limit ?? 100, 1), 500);
69
+ const offset = Math.max(params.offset ?? 0, 0);
70
+ values.push(limit, offset);
71
+ return db
72
+ .prepare(`SELECT *
73
+ FROM feedback_signals
74
+ WHERE ${where.join(" AND ")}
75
+ ORDER BY datetime(created_at) ASC, id ASC
76
+ LIMIT ? OFFSET ?`)
77
+ .all(...values);
78
+ }
79
+ /**
80
+ * Count unconsumed signals, optionally narrowed to one scope type. Drives the
81
+ * `GET /api/feedback/lessons` "N signals awaiting tonight's consolidation"
82
+ * health figure (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5) without loading
83
+ * the rows. Uses the same `consumed_at IS NULL` partial index as
84
+ * {@link getPendingFeedbackSignals}.
85
+ */
86
+ export function countPendingFeedbackSignals(db, params = {}) {
87
+ const where = ["consumed_at IS NULL"];
88
+ const values = [];
89
+ if (params.scopeType !== undefined) {
90
+ where.push("scope_type = ?");
91
+ values.push(params.scopeType);
92
+ }
93
+ const row = db
94
+ .prepare(`SELECT COUNT(*) AS n
95
+ FROM feedback_signals
96
+ WHERE ${where.join(" AND ")}`)
97
+ .get(...values);
98
+ return row.n;
99
+ }
100
+ export function consumeFeedbackSignals(db, ids, lessonRef) {
101
+ if (ids.length === 0)
102
+ return { consumed: 0, notFound: [] };
103
+ const placeholders = ids.map(() => "?").join(",");
104
+ const existing = db
105
+ .prepare(`SELECT id
106
+ FROM feedback_signals
107
+ WHERE id IN (${placeholders}) AND consumed_at IS NULL`)
108
+ .all(...ids);
109
+ const existingIds = new Set(existing.map((row) => row.id));
110
+ const notFound = ids.filter((id) => !existingIds.has(id));
111
+ if (existingIds.size === 0)
112
+ return { consumed: 0, notFound };
113
+ const updateIds = Array.from(existingIds);
114
+ const updatePlaceholders = updateIds.map(() => "?").join(",");
115
+ const consumed = db
116
+ .prepare(`UPDATE feedback_signals
117
+ SET consumed_at = CURRENT_TIMESTAMP, lesson_ref = COALESCE(?, lesson_ref)
118
+ WHERE id IN (${updatePlaceholders}) AND consumed_at IS NULL`)
119
+ .run(lessonRef ?? null, ...updateIds).changes;
120
+ return { consumed, notFound };
121
+ }
122
+ export function sweepConsumedFeedbackSignals(db, cutoff) {
123
+ return db
124
+ .prepare(`DELETE FROM feedback_signals
125
+ WHERE consumed_at IS NOT NULL
126
+ AND datetime(consumed_at) < datetime(?)`)
127
+ .run(cutoff).changes;
128
+ }
129
+ /**
130
+ * Compute the retention cutoff ISO timestamp for
131
+ * {@link sweepConsumedFeedbackSignals} from the `feedbackSignalRetentionDays`
132
+ * knob. Returns `null` when the knob is missing or non-finite (NaN / Infinity),
133
+ * so the caller degrades to "skip the sweep" instead of throwing on
134
+ * `new Date(NaN).toISOString()` and abandoning the whole nightly consolidation
135
+ * (FEEDBACK_LEARNING_LOOP_DESIGN.md §11 v1.3 robustness). `nowMs` is injected so
136
+ * the math is deterministically testable.
137
+ */
138
+ export function feedbackRetentionCutoff(retentionDays, nowMs) {
139
+ if (typeof retentionDays !== "number" || !Number.isFinite(retentionDays)) {
140
+ return null;
141
+ }
142
+ const retentionMs = retentionDays * 24 * 60 * 60 * 1000;
143
+ return new Date(nowMs - retentionMs).toISOString();
144
+ }
@@ -379,6 +379,56 @@ export const MIGRATIONS = [
379
379
  db.exec("UPDATE agent_schedule SET task_prompt = task_description WHERE task_prompt IS NULL");
380
380
  },
381
381
  },
382
+ {
383
+ id: "0009-today-refresh-budget-bump",
384
+ description: "(v0.1.9→next) — raise the routine.today_refresh per-turn budget "
385
+ + "ceiling from the seeded $0.30 to $0.50 for upgrading installs still "
386
+ + "on the seeded default. The drift-triggered today.md ## User Schedule "
387
+ + "refresh reads up to 200 pending calendar observations and retries the "
388
+ + "section PATCH up to 3x with 30s backoffs when the morning-routine lock "
389
+ + "is held; a busy-calendar drift compounded by that retry loop tipped a "
390
+ + "real run past $0.30 and surfaced BackendQuotaError(max_budget_usd) with "
391
+ + "no fallback (claude is the only binding) — the prior $0.10→$0.30 bump "
392
+ + "did not hold. Backend-aware: applyDefaultPresets stores the post-hoc-"
393
+ + "scaled budget (codex/gemini medium x1.5), so the OLD default is $0.30 "
394
+ + "on claude/opencode and $0.45 on codex/gemini, and the NEW default is "
395
+ + "the $0.50 base scaled the same way -> $0.50 / $0.75. Fresh installs "
396
+ + "already get the new value from the schema seed + the per-process "
397
+ + "envelope-overrides map; this migration only touches pre-existing "
398
+ + "installs. Gated so it ONLY moves preset rows still at the OLD per-"
399
+ + "backend default — operator-pinned rows (updated_by='user') and rows "
400
+ + "already at a custom value are left untouched. Idempotent: after the "
401
+ + "bump no row sits in the old band, and the recorded id short-circuits a "
402
+ + "re-run anyway.",
403
+ up(db) {
404
+ // Empty-DB safety (e.g. the runner's own unit tests run on a bare
405
+ // :memory: db): if applySchema never ran, the table is absent — the
406
+ // runner still records the id so a later boot does not re-evaluate.
407
+ if (!tableExists(db, "process_backend_config"))
408
+ return;
409
+ // The NEW per-backend value mirrors what `resolveDefaultBindingFor`
410
+ // now produces for routine.today_refresh: the $0.50 base x the medium
411
+ // post-hoc factor (1.5 for codex/gemini, 1.0 for claude/opencode). The
412
+ // 0.75 literal is that product at migration time — a migration is a
413
+ // point-in-time snapshot, so the literal is correct even if the factor
414
+ // later changes. The old-default bands ([0.29,0.31] / [0.44,0.46]) keep
415
+ // us from clobbering a row already moved to a custom value while still
416
+ // tolerating float dust.
417
+ db.prepare(`UPDATE process_backend_config
418
+ SET max_budget_usd = CASE
419
+ WHEN main_backend IN ('codex', 'gemini') THEN 0.75
420
+ ELSE 0.5
421
+ END
422
+ WHERE process_key = 'routine.today_refresh'
423
+ AND updated_by = 'preset'
424
+ AND (
425
+ (main_backend IN ('codex', 'gemini')
426
+ AND max_budget_usd >= 0.44 AND max_budget_usd <= 0.46)
427
+ OR (main_backend NOT IN ('codex', 'gemini')
428
+ AND max_budget_usd >= 0.29 AND max_budget_usd <= 0.31)
429
+ )`).run();
430
+ },
431
+ },
382
432
  ];
383
433
  /**
384
434
  * Ensure the `schema_migrations` bookkeeping table exists, then apply every
package/dist/db/schema.js CHANGED
@@ -382,6 +382,34 @@ CREATE TABLE IF NOT EXISTS notification_log (
382
382
  CREATE INDEX IF NOT EXISTS idx_notification_dispatch
383
383
  ON notification_log(dispatch_id, platform);
384
384
 
385
+ -- ── Feedback Learning Loop (FEEDBACK_LEARNING_LOOP_DESIGN.md Phase 1) ────────
386
+ --
387
+ -- Typed, append-only raw feedback signals. This is the structured sibling of
388
+ -- identity/profile.md "Raw Signals": behavioral notification outcomes, explicit
389
+ -- owner directives, and review-routine self-critique all land here before the
390
+ -- nightly consolidation pass promotes them into scoped lessons.
391
+ CREATE TABLE IF NOT EXISTS feedback_signals (
392
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
393
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
394
+ source TEXT NOT NULL
395
+ CHECK (source IN ('behavioral', 'explicit', 'self_critique')),
396
+ valence TEXT
397
+ CHECK (valence IN ('positive', 'negative', 'neutral', 'correction')),
398
+ scope_type TEXT NOT NULL
399
+ CHECK (scope_type IN ('user', 'agent', 'agent_slug', 'channel', 'task', 'integration')),
400
+ scope_ref TEXT,
401
+ action_kind TEXT,
402
+ action_ref TEXT,
403
+ agent_id TEXT,
404
+ summary TEXT NOT NULL,
405
+ evidence_json JSON DEFAULT '{}',
406
+ consumed_at TIMESTAMP,
407
+ lesson_ref TEXT
408
+ );
409
+ CREATE INDEX IF NOT EXISTS idx_feedback_unconsumed
410
+ ON feedback_signals(consumed_at, scope_type, scope_ref)
411
+ WHERE consumed_at IS NULL;
412
+
385
413
  CREATE TABLE IF NOT EXISTS owner_channels (
386
414
  platform TEXT PRIMARY KEY,
387
415
  sender_id TEXT,
@@ -2137,12 +2165,21 @@ VALUES
2137
2165
  ('routine.morning_routine_today', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.50, 'preset'),
2138
2166
  ('routine.morning_routine_journal', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 20, 0.30, 'preset'),
2139
2167
  ('routine.hourly_check', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.00, 'preset'),
2140
- -- $0.30 budget: a typical drift-triggered refresh on Sonnet runs ~$0.10
2141
- -- in 4 turns; the previous $0.10 cap left zero headroom and any larger
2142
- -- today.md / longer drift summary tipped over the SDK max_budget guard
2143
- -- and surfaced as BackendQuotaError(max_budget_usd). 3x observed cost
2144
- -- matches the headroom convention used by dashboard.docs_qa.
2145
- ('routine.today_refresh', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 20, 0.30, 'preset'),
2168
+ -- $0.50 budget: a typical drift-triggered refresh on Sonnet runs ~$0.10
2169
+ -- in 4 turns, but a busy-calendar drift (many/large pending calendar
2170
+ -- observations read via the task-flow GET limit=200) compounded by a
2171
+ -- 409 morning-lock retry loop (3x with 30s backoffs) tipped a real run
2172
+ -- past the prior $0.30 cap and surfaced as BackendQuotaError(max_budget_usd)
2173
+ -- with no fallback (claude is the only binding). The previous $0.10→$0.30
2174
+ -- bump did not hold. Realigned to $0.50 — the medium-tier 20-turn peer
2175
+ -- dashboard.docs_qa value, and well under the superset
2176
+ -- routine.morning_routine_today ($1.50), which writes the full today.md
2177
+ -- from the same calendar data. A third trip should drive a
2178
+ -- work-reduction fix (tighter GET limit / cheaper 409 backoff), not
2179
+ -- another blind bump. Bumped for upgrading installs by migration 0009.
2180
+ -- Keep in lock-step with ENVELOPE_OVERRIDES_BY_PROCESS_KEY in
2181
+ -- plan-presets.ts.
2182
+ ('routine.today_refresh', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 20, 0.50, 'preset'),
2146
2183
  ('routine.evening_review', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.00, 'preset'),
2147
2184
  ('routine.weekly_review', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.00, 'preset'),
2148
2185
  -- routine.monthly_review: row seeded but the routine is gated OFF by
@@ -36,7 +36,7 @@
36
36
  * are matched as shell-command globs, Read/Write/Edit arguments as path
37
37
  * globs.
38
38
  */
39
- export declare const ALWAYS_DISALLOWED_TOOLS: readonly ["Bash(rm -rf *)", "Bash(rm -rf /*)", "Bash(rm -rf ~*)", "Bash(rm -rf .*)", "Bash(rm -r *)", "Bash(rm -rf*)", "Bash(rm -rv*)", "Bash(rm -ri*)", "Bash(rm -rd*)", "Bash(rm -rI*)", "Bash(rm -fr*)", "Bash(rm -vr*)", "Bash(rm -ir*)", "Bash(rm -dr*)", "Bash(rm -Ir*)", "Bash(rm -R*)", "Bash(rm -fR*)", "Bash(rm -vR*)", "Bash(rm -iR*)", "Bash(rm -dR*)", "Bash(rm -IR*)", "Bash(rm --recursive*)", "Bash(rm --force --recursive*)", "Bash(rm --recursive --force*)", "Bash(sudo *)", "Bash(doas *)", "Bash(su *)", "Bash(curl * | sh*)", "Bash(curl * | bash*)", "Bash(wget * | sh*)", "Bash(wget * | bash*)", "Bash(bash <(*)*)", "Bash(sh <(*)*)", "Bash(bash<*)", "Bash(sh<*)", "Bash(eval *)", "Bash(source *)", "Bash(security *)", "Bash(secret-tool *)", "Bash(cmdkey *)", "Bash(certutil *)", "Bash(rundll32.exe *)", "Read(.env)", "Read(.env.*)", "Read(**/.env)", "Read(**/.env.*)", "Read(id_rsa*)", "Read(id_ed25519*)", "Read(~/.ssh/**)", "Read(~/.gnupg/**)", "Read(~/.aws/**)", "Read(~/.config/gcloud/**)", "Read(~/.config/gh/hosts.yml)", "Read(~/.netrc)", "Read(~/Library/Keychains/**)", "Read(~/.local/share/keyrings/**)", "Read(~/.personal-agent/backups/**)", "Read(~/.personal-agent/whatsapp/auth/**)", "Read(~/.personal-agent/secrets/**)", "Write(.env)", "Edit(.env)", "Write(.env.*)", "Edit(.env.*)", "Write(**/.env)", "Edit(**/.env)", "Write(**/.env.*)", "Edit(**/.env.*)", "Write(id_rsa*)", "Edit(id_rsa*)", "Write(id_ed25519*)", "Edit(id_ed25519*)", "Write(~/.ssh/**)", "Edit(~/.ssh/**)", "Write(~/.gnupg/**)", "Edit(~/.gnupg/**)", "Write(~/.aws/**)", "Edit(~/.aws/**)", "Write(~/.config/gcloud/**)", "Edit(~/.config/gcloud/**)", "Write(~/.config/gh/hosts.yml)", "Edit(~/.config/gh/hosts.yml)", "Write(~/.netrc)", "Edit(~/.netrc)", "Write(~/Library/Keychains/**)", "Edit(~/Library/Keychains/**)", "Write(~/.local/share/keyrings/**)", "Edit(~/.local/share/keyrings/**)", "Write(~/.personal-agent/backups/**)", "Edit(~/.personal-agent/backups/**)", "Write(~/.personal-agent/whatsapp/auth/**)", "Edit(~/.personal-agent/whatsapp/auth/**)", "Write(~/.personal-agent/secrets/**)", "Edit(~/.personal-agent/secrets/**)", "Bash(sqlite3 *)", "Bash(cp ~/Library/Application Support/Google/Chrome/*)", "Bash(cp ~/Library/Application Support/Chromium/*)", "Bash(cp ~/Library/Application Support/Microsoft Edge/*)", "Bash(cp ~/Library/Application Support/BraveSoftware/*)", "Bash(cp ~/Library/Application Support/Comet/*)", "Bash(cp ~/Library/Application Support/Perplexity Comet/*)", "Bash(cp ~/Library/Application Support/com.openai.atlas/*)", "Bash(cp ~/.config/google-chrome/*)", "Bash(cp ~/.config/chromium/*)", "Bash(cp ~/.config/microsoft-edge/*)", "Bash(cp ~/.config/BraveSoftware/*)", "Bash(cp ~/.config/Comet/*)", "Bash(cp ~/.var/app/com.google.Chrome/*)", "Bash(cp /mnt/c/Users/*)", "Bash(curl file://*)", "Read(~/Library/Application Support/Google/Chrome/**)", "Read(~/Library/Application Support/Chromium/**)", "Read(~/Library/Application Support/Microsoft Edge/**)", "Read(~/Library/Application Support/BraveSoftware/**)", "Read(~/Library/Application Support/Comet/**)", "Read(~/Library/Application Support/Perplexity Comet/**)", "Read(~/Library/Application Support/com.openai.atlas/**)", "Read(~/.config/google-chrome/**)", "Read(~/.config/chromium/**)", "Read(~/.config/microsoft-edge/**)", "Read(~/.config/BraveSoftware/**)", "Read(~/.config/Comet/**)", "Read(~/.var/app/com.google.Chrome/**)", "Read(/mnt/c/Users/**)", "Write(~/Library/Application Support/Google/Chrome/**)", "Edit(~/Library/Application Support/Google/Chrome/**)", "Write(~/Library/Application Support/Chromium/**)", "Edit(~/Library/Application Support/Chromium/**)", "Write(~/Library/Application Support/Microsoft Edge/**)", "Edit(~/Library/Application Support/Microsoft Edge/**)", "Write(~/Library/Application Support/BraveSoftware/**)", "Edit(~/Library/Application Support/BraveSoftware/**)", "Write(~/Library/Application Support/Comet/**)", "Edit(~/Library/Application Support/Comet/**)", "Write(~/Library/Application Support/Perplexity Comet/**)", "Edit(~/Library/Application Support/Perplexity Comet/**)", "Write(~/Library/Application Support/com.openai.atlas/**)", "Edit(~/Library/Application Support/com.openai.atlas/**)", "Write(~/.config/google-chrome/**)", "Edit(~/.config/google-chrome/**)", "Write(~/.config/chromium/**)", "Edit(~/.config/chromium/**)", "Write(~/.config/microsoft-edge/**)", "Edit(~/.config/microsoft-edge/**)", "Write(~/.config/BraveSoftware/**)", "Edit(~/.config/BraveSoftware/**)", "Write(~/.config/Comet/**)", "Edit(~/.config/Comet/**)", "Write(~/.var/app/com.google.Chrome/**)", "Edit(~/.var/app/com.google.Chrome/**)", "Write(/mnt/c/Users/**)", "Edit(/mnt/c/Users/**)", "Bash(cp ~/.personal-agent/chromium-*)", "Bash(mv ~/.personal-agent/chromium-*)", "Bash(tar ~/.personal-agent/chromium-*)", "Bash(zip ~/.personal-agent/chromium-*)", "Bash(rsync ~/.personal-agent/chromium-*)", "Bash(cp $HOME/.personal-agent/chromium-*)", "Bash(mv $HOME/.personal-agent/chromium-*)", "Bash(tar $HOME/.personal-agent/chromium-*)", "Bash(zip $HOME/.personal-agent/chromium-*)", "Bash(rsync $HOME/.personal-agent/chromium-*)", "Read(~/.personal-agent/chromium-sync/**)", "Read(~/.personal-agent/chromium-automation/**)", "Read(~/.personal-agent/chromium-automation-anon/**)", "Read(~/.personal-agent/chromium-automation-auth/**)", "Read(~/.personal-agent/chromium-automation-purchase/**)", "Write(~/.personal-agent/chromium-sync/**)", "Edit(~/.personal-agent/chromium-sync/**)", "Write(~/.personal-agent/chromium-automation/**)", "Edit(~/.personal-agent/chromium-automation/**)", "Write(~/.personal-agent/chromium-automation-anon/**)", "Edit(~/.personal-agent/chromium-automation-anon/**)", "Write(~/.personal-agent/chromium-automation-auth/**)", "Edit(~/.personal-agent/chromium-automation-auth/**)", "Write(~/.personal-agent/chromium-automation-purchase/**)", "Edit(~/.personal-agent/chromium-automation-purchase/**)", "CronCreate", "CronList", "CronDelete", "RemoteTrigger", "PushNotification"];
39
+ export declare const ALWAYS_DISALLOWED_TOOLS: readonly ["Bash(rm -rf *)", "Bash(rm -rf /*)", "Bash(rm -rf ~*)", "Bash(rm -rf .*)", "Bash(rm -r *)", "Bash(rm -rf*)", "Bash(rm -rv*)", "Bash(rm -ri*)", "Bash(rm -rd*)", "Bash(rm -rI*)", "Bash(rm -fr*)", "Bash(rm -vr*)", "Bash(rm -ir*)", "Bash(rm -dr*)", "Bash(rm -Ir*)", "Bash(rm -R*)", "Bash(rm -fR*)", "Bash(rm -vR*)", "Bash(rm -iR*)", "Bash(rm -dR*)", "Bash(rm -IR*)", "Bash(rm --recursive*)", "Bash(rm --force --recursive*)", "Bash(rm --recursive --force*)", "Bash(sudo *)", "Bash(doas *)", "Bash(su *)", "Bash(curl * | sh*)", "Bash(curl * | bash*)", "Bash(wget * | sh*)", "Bash(wget * | bash*)", "Bash(bash <(*)*)", "Bash(sh <(*)*)", "Bash(bash<*)", "Bash(sh<*)", "Bash(eval *)", "Bash(source *)", "Bash(security *)", "Bash(secret-tool *)", "Bash(cmdkey *)", "Bash(certutil *)", "Bash(rundll32.exe *)", "Read(.env)", "Read(.env.*)", "Read(**/.env)", "Read(**/.env.*)", "Read(id_rsa*)", "Read(id_ed25519*)", "Read(~/.ssh/**)", "Read(~/.gnupg/**)", "Read(~/.aws/**)", "Read(~/.config/gcloud/**)", "Read(~/.config/gh/hosts.yml)", "Read(~/.netrc)", "Read(~/Library/Keychains/**)", "Read(~/.local/share/keyrings/**)", "Read(~/.personal-agent/backups/**)", "Read(~/.personal-agent/whatsapp/auth/**)", "Read(~/.personal-agent/secrets/**)", "Read(~/.claude/.credentials.json)", "Read(~/.claude.json)", "Read(~/.codex/auth.json)", "Read(~/.gemini/gemini-credentials.json)", "Read(~/.gemini/oauth_creds.json)", "Read(~/.config/anthropic/**)", "Write(.env)", "Edit(.env)", "Write(.env.*)", "Edit(.env.*)", "Write(**/.env)", "Edit(**/.env)", "Write(**/.env.*)", "Edit(**/.env.*)", "Write(id_rsa*)", "Edit(id_rsa*)", "Write(id_ed25519*)", "Edit(id_ed25519*)", "Write(~/.ssh/**)", "Edit(~/.ssh/**)", "Write(~/.gnupg/**)", "Edit(~/.gnupg/**)", "Write(~/.aws/**)", "Edit(~/.aws/**)", "Write(~/.config/gcloud/**)", "Edit(~/.config/gcloud/**)", "Write(~/.config/gh/hosts.yml)", "Edit(~/.config/gh/hosts.yml)", "Write(~/.netrc)", "Edit(~/.netrc)", "Write(~/Library/Keychains/**)", "Edit(~/Library/Keychains/**)", "Write(~/.local/share/keyrings/**)", "Edit(~/.local/share/keyrings/**)", "Write(~/.personal-agent/backups/**)", "Edit(~/.personal-agent/backups/**)", "Write(~/.personal-agent/whatsapp/auth/**)", "Edit(~/.personal-agent/whatsapp/auth/**)", "Write(~/.personal-agent/secrets/**)", "Edit(~/.personal-agent/secrets/**)", "Write(~/.claude/.credentials.json)", "Edit(~/.claude/.credentials.json)", "Write(~/.claude.json)", "Edit(~/.claude.json)", "Write(~/.codex/auth.json)", "Edit(~/.codex/auth.json)", "Write(~/.gemini/gemini-credentials.json)", "Edit(~/.gemini/gemini-credentials.json)", "Write(~/.gemini/oauth_creds.json)", "Edit(~/.gemini/oauth_creds.json)", "Write(~/.config/anthropic/**)", "Edit(~/.config/anthropic/**)", "Bash(sqlite3 *)", "Bash(cp ~/Library/Application Support/Google/Chrome/*)", "Bash(cp ~/Library/Application Support/Chromium/*)", "Bash(cp ~/Library/Application Support/Microsoft Edge/*)", "Bash(cp ~/Library/Application Support/BraveSoftware/*)", "Bash(cp ~/Library/Application Support/Comet/*)", "Bash(cp ~/Library/Application Support/Perplexity Comet/*)", "Bash(cp ~/Library/Application Support/com.openai.atlas/*)", "Bash(cp ~/.config/google-chrome/*)", "Bash(cp ~/.config/chromium/*)", "Bash(cp ~/.config/microsoft-edge/*)", "Bash(cp ~/.config/BraveSoftware/*)", "Bash(cp ~/.config/Comet/*)", "Bash(cp ~/.var/app/com.google.Chrome/*)", "Bash(cp /mnt/c/Users/*)", "Bash(curl file://*)", "Read(~/Library/Application Support/Google/Chrome/**)", "Read(~/Library/Application Support/Chromium/**)", "Read(~/Library/Application Support/Microsoft Edge/**)", "Read(~/Library/Application Support/BraveSoftware/**)", "Read(~/Library/Application Support/Comet/**)", "Read(~/Library/Application Support/Perplexity Comet/**)", "Read(~/Library/Application Support/com.openai.atlas/**)", "Read(~/.config/google-chrome/**)", "Read(~/.config/chromium/**)", "Read(~/.config/microsoft-edge/**)", "Read(~/.config/BraveSoftware/**)", "Read(~/.config/Comet/**)", "Read(~/.var/app/com.google.Chrome/**)", "Read(/mnt/c/Users/**)", "Write(~/Library/Application Support/Google/Chrome/**)", "Edit(~/Library/Application Support/Google/Chrome/**)", "Write(~/Library/Application Support/Chromium/**)", "Edit(~/Library/Application Support/Chromium/**)", "Write(~/Library/Application Support/Microsoft Edge/**)", "Edit(~/Library/Application Support/Microsoft Edge/**)", "Write(~/Library/Application Support/BraveSoftware/**)", "Edit(~/Library/Application Support/BraveSoftware/**)", "Write(~/Library/Application Support/Comet/**)", "Edit(~/Library/Application Support/Comet/**)", "Write(~/Library/Application Support/Perplexity Comet/**)", "Edit(~/Library/Application Support/Perplexity Comet/**)", "Write(~/Library/Application Support/com.openai.atlas/**)", "Edit(~/Library/Application Support/com.openai.atlas/**)", "Write(~/.config/google-chrome/**)", "Edit(~/.config/google-chrome/**)", "Write(~/.config/chromium/**)", "Edit(~/.config/chromium/**)", "Write(~/.config/microsoft-edge/**)", "Edit(~/.config/microsoft-edge/**)", "Write(~/.config/BraveSoftware/**)", "Edit(~/.config/BraveSoftware/**)", "Write(~/.config/Comet/**)", "Edit(~/.config/Comet/**)", "Write(~/.var/app/com.google.Chrome/**)", "Edit(~/.var/app/com.google.Chrome/**)", "Write(/mnt/c/Users/**)", "Edit(/mnt/c/Users/**)", "Bash(cp ~/.personal-agent/chromium-*)", "Bash(mv ~/.personal-agent/chromium-*)", "Bash(tar ~/.personal-agent/chromium-*)", "Bash(zip ~/.personal-agent/chromium-*)", "Bash(rsync ~/.personal-agent/chromium-*)", "Bash(cp $HOME/.personal-agent/chromium-*)", "Bash(mv $HOME/.personal-agent/chromium-*)", "Bash(tar $HOME/.personal-agent/chromium-*)", "Bash(zip $HOME/.personal-agent/chromium-*)", "Bash(rsync $HOME/.personal-agent/chromium-*)", "Read(~/.personal-agent/chromium-sync/**)", "Read(~/.personal-agent/chromium-automation/**)", "Read(~/.personal-agent/chromium-automation-anon/**)", "Read(~/.personal-agent/chromium-automation-auth/**)", "Read(~/.personal-agent/chromium-automation-purchase/**)", "Write(~/.personal-agent/chromium-sync/**)", "Edit(~/.personal-agent/chromium-sync/**)", "Write(~/.personal-agent/chromium-automation/**)", "Edit(~/.personal-agent/chromium-automation/**)", "Write(~/.personal-agent/chromium-automation-anon/**)", "Edit(~/.personal-agent/chromium-automation-anon/**)", "Write(~/.personal-agent/chromium-automation-auth/**)", "Edit(~/.personal-agent/chromium-automation-auth/**)", "Write(~/.personal-agent/chromium-automation-purchase/**)", "Edit(~/.personal-agent/chromium-automation-purchase/**)", "CronCreate", "CronList", "CronDelete", "RemoteTrigger", "PushNotification"];
40
40
  /**
41
41
  * Structured classification of an attempted command or path against the
42
42
  * absolute-block list, used by the §6.3 audit path.