@aitne-sh/aitne 0.1.9 → 0.1.11

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 (218) hide show
  1. package/README.md +41 -11
  2. package/agent-assets/agent-profiles/background-task.md +53 -0
  3. package/agent-assets/agent-profiles/conversational.md +1 -0
  4. package/agent-assets/agent-profiles/routine-fetch-window.md +14 -75
  5. package/agent-assets/agent-profiles/routine.md +1 -1
  6. package/agent-assets/agents/{hourly-check → activity-scan}/agent.md +22 -17
  7. package/agent-assets/agents/monthly-review/agent.md +6 -5
  8. package/agent-assets/docs/concepts/agent-day.md +6 -7
  9. package/agent-assets/docs/concepts/auth-health.md +23 -20
  10. package/agent-assets/docs/concepts/backends-and-tiers.md +13 -9
  11. package/agent-assets/docs/concepts/costs-and-quotas.md +14 -12
  12. package/agent-assets/docs/concepts/delegated-mode.md +18 -17
  13. package/agent-assets/docs/concepts/memory-model.md +16 -9
  14. package/agent-assets/docs/concepts/observations.md +24 -20
  15. package/agent-assets/docs/concepts/process-keys.md +10 -9
  16. package/agent-assets/docs/concepts/routines.md +34 -31
  17. package/agent-assets/docs/concepts/safety-and-execution.md +11 -7
  18. package/agent-assets/docs/concepts/safety-model.md +39 -25
  19. package/agent-assets/docs/concepts/skills.md +12 -10
  20. package/agent-assets/docs/features/integrations/browser-history.md +23 -18
  21. package/agent-assets/docs/features/integrations/calendar.md +28 -17
  22. package/agent-assets/docs/features/integrations/git.md +13 -11
  23. package/agent-assets/docs/features/integrations/github.md +22 -14
  24. package/agent-assets/docs/features/integrations/mail.md +25 -22
  25. package/agent-assets/docs/features/integrations/notion.md +35 -11
  26. package/agent-assets/docs/features/integrations/obsidian.md +8 -8
  27. package/agent-assets/docs/features/lifestyle/git.md +27 -23
  28. package/agent-assets/docs/features/lifestyle/reading.md +20 -11
  29. package/agent-assets/docs/features/lifestyle/receipts.md +11 -10
  30. package/agent-assets/docs/features/lifestyle/travel-bookings.md +4 -3
  31. package/agent-assets/docs/features/memory-files/agent-journal.md +53 -26
  32. package/agent-assets/docs/features/memory-files/agent-lessons.md +178 -0
  33. package/agent-assets/docs/features/memory-files/projects.md +11 -8
  34. package/agent-assets/docs/features/memory-files/roadmap.md +17 -14
  35. package/agent-assets/docs/features/memory-files/schedule.md +6 -3
  36. package/agent-assets/docs/features/memory-files/today.md +10 -7
  37. package/agent-assets/docs/features/memory-files/user-profile.md +14 -9
  38. package/agent-assets/docs/features/messaging/bang-commands.md +21 -6
  39. package/agent-assets/docs/features/messaging/overview.md +17 -14
  40. package/agent-assets/docs/features/messaging/telegram.md +10 -9
  41. package/agent-assets/docs/features/operations/activity-and-conversations.md +6 -5
  42. package/agent-assets/docs/features/operations/approvals.md +6 -5
  43. package/agent-assets/docs/features/operations/browser-tasks.md +184 -0
  44. package/agent-assets/docs/features/operations/cost-tracking.md +11 -1
  45. package/agent-assets/docs/features/operations/managed-chromium.md +4 -2
  46. package/agent-assets/docs/features/operations/notifications.md +11 -1
  47. package/agent-assets/docs/features/operations/quiet-hours.md +41 -11
  48. package/agent-assets/docs/features/operations/schedule-approaching.md +2 -2
  49. package/agent-assets/docs/features/routines/activity-scan.md +220 -0
  50. package/agent-assets/docs/features/routines/custom-routines.md +82 -134
  51. package/agent-assets/docs/features/routines/evening-review.md +23 -13
  52. package/agent-assets/docs/features/routines/morning-routine.md +7 -5
  53. package/agent-assets/docs/features/routines/weekly-review.md +24 -3
  54. package/agent-assets/docs/features/wiki/commands.md +4 -4
  55. package/agent-assets/docs/features/wiki/cost-and-approval.md +4 -3
  56. package/agent-assets/docs/features/wiki/dashboard.md +7 -6
  57. package/agent-assets/docs/features/wiki/overview.md +3 -3
  58. package/agent-assets/docs/features/wiki/search.md +5 -5
  59. package/agent-assets/docs/features/wiki/workspaces.md +2 -2
  60. package/agent-assets/docs/getting-started/01-what-is-this.md +3 -3
  61. package/agent-assets/docs/getting-started/02-first-steps.md +5 -3
  62. package/agent-assets/docs/getting-started/04-first-day.md +27 -30
  63. package/agent-assets/docs/glossary.md +8 -8
  64. package/agent-assets/docs/guides/add-a-custom-routine.md +122 -68
  65. package/agent-assets/docs/guides/budget-and-cost-for-wiki.md +2 -2
  66. package/agent-assets/docs/guides/connect-a-new-mail-account.md +4 -2
  67. package/agent-assets/docs/guides/explore-with-trace-and-connect.md +5 -4
  68. package/agent-assets/docs/guides/install-and-run.md +2 -2
  69. package/agent-assets/docs/guides/maintain-wiki-health.md +2 -2
  70. package/agent-assets/docs/guides/pause-the-agent.md +27 -21
  71. package/agent-assets/docs/guides/reinstall-cleanly.md +2 -0
  72. package/agent-assets/docs/guides/setup-wizard.md +12 -6
  73. package/agent-assets/docs/guides/use-an-existing-obsidian-vault.md +6 -6
  74. package/agent-assets/docs/reference/api.md +26 -5
  75. package/agent-assets/docs/reference/cli-commands.md +3 -3
  76. package/agent-assets/docs/reference/config.md +51 -24
  77. package/agent-assets/docs/reference/disallowed-tools.md +6 -4
  78. package/agent-assets/docs/reference/keyboard-shortcuts.md +2 -2
  79. package/agent-assets/docs/reference/knowledge-layout.md +25 -12
  80. package/agent-assets/docs/reference/process-keys.md +9 -9
  81. package/agent-assets/docs/reference/skills.md +10 -6
  82. package/agent-assets/docs/troubleshooting/auth-failed.md +9 -8
  83. package/agent-assets/docs/troubleshooting/dashboard-shows-degraded.md +16 -9
  84. package/agent-assets/docs/troubleshooting/messaging-not-pairing.md +2 -2
  85. package/agent-assets/docs/troubleshooting/morning-routine-didnt-run.md +32 -16
  86. package/agent-assets/docs/troubleshooting/observation-not-detected.md +26 -24
  87. package/agent-assets/docs/troubleshooting/quota-exhausted.md +7 -6
  88. package/agent-assets/docs/troubleshooting/wiki-write-failed.md +3 -3
  89. package/agent-assets/skills/agent-actions/SKILL.md +23 -39
  90. package/agent-assets/skills/agent-create/SKILL.md +26 -6
  91. package/agent-assets/skills/attach/SKILL.md +8 -27
  92. package/agent-assets/skills/background-task/SKILL.md +184 -0
  93. package/agent-assets/skills/background-task-reply/SKILL.md +100 -0
  94. package/agent-assets/skills/browser-history/SKILL.md +60 -29
  95. package/agent-assets/skills/browser-history-respond/SKILL.md +6 -1
  96. package/agent-assets/skills/browser-task/SKILL.md +33 -31
  97. package/agent-assets/skills/context/SKILL.md +26 -34
  98. package/agent-assets/skills/context/curation.json +12 -12
  99. package/agent-assets/skills/context/references/api.md +22 -20
  100. package/agent-assets/skills/context/references/required-frontmatter.md +10 -9
  101. package/agent-assets/skills/context/references/snapshot-files.md +16 -15
  102. package/agent-assets/skills/context/seeds/file-responsibilities.seed.json +5 -5
  103. package/agent-assets/skills/context/seeds/frontmatter-requirements.seed.json +3 -3
  104. package/agent-assets/skills/docs-search/SKILL.md +19 -31
  105. package/agent-assets/skills/external-services/SKILL.delegated.claude.md +8 -95
  106. package/agent-assets/skills/external-services/SKILL.delegated.codex.md +8 -94
  107. package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +8 -94
  108. package/agent-assets/skills/external-services/SKILL.native.claude.md +15 -9
  109. package/agent-assets/skills/external-services/SKILL.native.codex.md +11 -5
  110. package/agent-assets/skills/external-services/SKILL.native.gemini.md +11 -5
  111. package/agent-assets/skills/external-services/references/exec-errors.md +32 -0
  112. package/agent-assets/skills/external-services/references/skills-crud.md +5 -5
  113. package/agent-assets/skills/gmail-lifestyle/SKILL.md +3 -2
  114. package/agent-assets/skills/gmail-lifestyle/references/receipts-api.md +4 -0
  115. package/agent-assets/skills/gmail-lifestyle/references/travel-bookings-api.md +9 -0
  116. package/agent-assets/skills/mail/SKILL.delegated.claude.md +13 -25
  117. package/agent-assets/skills/mail/SKILL.delegated.codex.md +3 -2
  118. package/agent-assets/skills/mail/SKILL.delegated.gemini.md +3 -2
  119. package/agent-assets/skills/mail/SKILL.md +12 -20
  120. package/agent-assets/skills/mail/SKILL.native.claude.md +24 -16
  121. package/agent-assets/skills/mail/SKILL.native.codex.md +16 -9
  122. package/agent-assets/skills/mail/SKILL.native.gemini.md +12 -6
  123. package/agent-assets/skills/mail/references/api.md +6 -1
  124. package/agent-assets/skills/mail/references/examples.md +2 -1
  125. package/agent-assets/skills/managed-tasks/SKILL.md +44 -77
  126. package/agent-assets/skills/managed-tasks/references/errors.md +25 -14
  127. package/agent-assets/skills/managed-tasks/references/output-path.md +33 -17
  128. package/agent-assets/skills/managed-tasks/references/recurrence-rule.md +26 -16
  129. package/agent-assets/skills/management-policy/SKILL.md +36 -28
  130. package/agent-assets/skills/management-policy/curation.json +1 -1
  131. package/agent-assets/skills/management-policy/references/policy-workflow.md +30 -18
  132. package/agent-assets/skills/notify/SKILL.md +16 -13
  133. package/agent-assets/skills/notify/references/priority.md +42 -26
  134. package/agent-assets/skills/notion/SKILL.delegated.claude.md +1 -1
  135. package/agent-assets/skills/notion/SKILL.delegated.codex.md +1 -1
  136. package/agent-assets/skills/notion/SKILL.delegated.gemini.md +1 -1
  137. package/agent-assets/skills/notion/SKILL.md +18 -18
  138. package/agent-assets/skills/notion/SKILL.native.claude.md +4 -4
  139. package/agent-assets/skills/notion/SKILL.native.codex.md +3 -3
  140. package/agent-assets/skills/notion/SKILL.native.gemini.md +3 -3
  141. package/agent-assets/skills/observations/SKILL.md +9 -24
  142. package/agent-assets/skills/observations/references/fetch-fallback.md +22 -0
  143. package/agent-assets/skills/project-doc/SKILL.md +9 -6
  144. package/agent-assets/skills/project-doc/curation.json +3 -3
  145. package/agent-assets/skills/project-doc/seeds/project-shape.seed.json +2 -2
  146. package/agent-assets/skills/project-doc/seeds/slug-grammar.seed.json +3 -3
  147. package/agent-assets/skills/reading/SKILL.md +8 -42
  148. package/agent-assets/skills/reading/references/reading-taste.md +5 -5
  149. package/agent-assets/skills/roadmap/SKILL.md +3 -19
  150. package/agent-assets/skills/roadmap/references/api.md +23 -8
  151. package/agent-assets/skills/roadmap/references/horizon-tags.md +11 -0
  152. package/agent-assets/skills/roadmap/references/migration.md +8 -6
  153. package/agent-assets/skills/roadmap/references/retention.md +18 -0
  154. package/agent-assets/skills/schedule/SKILL.md +20 -28
  155. package/agent-assets/skills/schedule/references/importance.md +23 -0
  156. package/agent-assets/skills/schedule/references/recurrence-rule.md +26 -16
  157. package/agent-assets/skills/scheduled-managed-task/SKILL.md +46 -46
  158. package/agent-assets/skills/today/SKILL.md +38 -81
  159. package/agent-assets/skills/today/references/agent-plan-lifecycle.md +9 -4
  160. package/agent-assets/skills/today/references/agent-plan-revision.md +28 -0
  161. package/agent-assets/skills/today/references/today-skeleton.md +66 -0
  162. package/agent-assets/skills/today/seeds/agent-notes-flavors.seed.json +1 -1
  163. package/agent-assets/skills/today/seeds/section-shape.seed.json +6 -6
  164. package/agent-assets/skills/user-interview/SKILL.md +15 -90
  165. package/agent-assets/skills/user-interview/references/op-briefing.md +1 -1
  166. package/agent-assets/skills/user-interview/references/op-dm-handler.md +88 -0
  167. package/agent-assets/skills/user-interview/references/op-morning.md +2 -2
  168. package/agent-assets/skills/user-interview/references/sweep-and-fallback.md +1 -1
  169. package/agent-assets/skills/user-profile/SKILL.md +16 -26
  170. package/agent-assets/skills/user-profile/curation.json +3 -3
  171. package/agent-assets/skills/user-profile/references/character-preferences.md +3 -3
  172. package/agent-assets/skills/wiki/wiki-ask/SKILL.md +1 -1
  173. package/agent-assets/skills/wiki/wiki-compile/SKILL.md +5 -4
  174. package/agent-assets/skills/wiki/wiki-connect/SKILL.md +32 -5
  175. package/agent-assets/skills/wiki/wiki-ingest/SKILL.md +6 -50
  176. package/agent-assets/skills/wiki/wiki-ingest/references/curl-errors.md +58 -0
  177. package/agent-assets/skills/wiki/wiki-lint/SKILL.md +20 -14
  178. package/agent-assets/skills/wiki/wiki-trace/SKILL.md +10 -5
  179. package/agent-assets/skills/wiki/wiki-vault-rules/SKILL.md +2 -0
  180. package/agent-assets/system-prompts/routine-research-cluster-update.md +71 -0
  181. package/agent-assets/task-flows/_partials/feedback-capture.md +30 -0
  182. package/agent-assets/task-flows/_partials/notion-acquire.notion.md +47 -21
  183. package/agent-assets/task-flows/background_task.md +81 -0
  184. package/agent-assets/task-flows/git.local_ahead.stale.md +1 -1
  185. package/agent-assets/task-flows/git.push.detected.md +1 -1
  186. package/agent-assets/task-flows/git.tag.created.md +1 -1
  187. package/agent-assets/task-flows/github.assigned.md +1 -1
  188. package/agent-assets/task-flows/github.pull_request.review_requested.md +2 -2
  189. package/agent-assets/task-flows/github.security_alert.md +1 -1
  190. package/agent-assets/task-flows/message.received.dm.md +11 -3
  191. package/agent-assets/task-flows/message.received.dm_first.md +8 -2
  192. package/agent-assets/task-flows/{routine.hourly_check.md → routine.activity_scan.md} +31 -23
  193. package/agent-assets/task-flows/{routine.hourly_check.triage.md → routine.activity_scan.triage.md} +3 -3
  194. package/agent-assets/task-flows/routine.evening_review.md +80 -0
  195. package/agent-assets/task-flows/routine.monthly_review.md +81 -8
  196. package/agent-assets/task-flows/routine.research_cluster_update.md +33 -19
  197. package/agent-assets/task-flows/routine.roadmap_refresh.md +2 -2
  198. package/agent-assets/task-flows/routine.today_refresh.md +1 -1
  199. package/agent-assets/task-flows/routine.weekly_review.md +124 -4
  200. package/agent-assets/task-flows/schedule.approaching.md +2 -2
  201. package/agent-assets/task-flows/scheduled.dm.md +77 -1
  202. package/agent-assets/task-flows/scheduled.task.md +7 -1
  203. package/agent-assets/task-flows/wiki.trace.md +1 -1
  204. package/agent-assets/templates/_manifest.json +2 -2
  205. package/agent-assets/templates/knowledge/dossiers/_index.md +1 -1
  206. package/agent-assets/templates/knowledge/dossiers/{hourly.md → activity-scan.md} +1 -1
  207. package/agent-assets/templates/policies/journal-format.md +1 -1
  208. package/agent-assets/templates/policies/mcp.md +1 -1
  209. package/agent-assets/templates/policies/routines/_index.md +1 -1
  210. package/agent-assets/templates/policies/routines/{hourly.md → activity-scan.md} +5 -5
  211. package/bin/aitne.mjs +45 -11
  212. package/package.json +6 -5
  213. package/scripts/commands/doctor.mjs +11 -2
  214. package/scripts/lib/process-identity.d.mts +46 -0
  215. package/scripts/lib/process-identity.mjs +193 -0
  216. package/scripts/lib/read-api-token.mjs +1 -1
  217. package/scripts/start.mjs +14 -4
  218. package/agent-assets/docs/features/routines/hourly-check.md +0 -205
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Process start-identity helpers for the Aitne launcher (`bin/aitne.mjs`).
3
+ *
4
+ * WHY THIS EXISTS — process-lifecycle-2 (CROSS_PLATFORM_REAUDIT_2026-06.md).
5
+ * The launcher trusts a bare PID: `process.kill(pid, 0)` is true for *any* live
6
+ * process owning that PID. After an unclean shutdown the stale `daemon.pid` /
7
+ * `dashboard.pid` can point at a PID the OS has recycled to an unrelated
8
+ * process, so `aitne start` false-positives "Already running" and `aitne stop`
9
+ * can `taskkill /T /F` (Windows) or group-kill (POSIX) the wrong process tree.
10
+ *
11
+ * FIX — pair the PID with the OS-reported process **start time**, captured at
12
+ * write time and re-read at check time. A recycled PID has a different start
13
+ * time, so an exact-string mismatch flags it stale. We compare the *same* OS
14
+ * field to *itself* on the *same* machine, so no absolute-time conversion,
15
+ * clock-skew tolerance, or locale handling is needed.
16
+ *
17
+ * WHERE THIS LIVES — `scripts/lib/` (plain ESM), because `bin/aitne.mjs` runs
18
+ * *before* the TypeScript build that produces `@aitne/shared` (running `aitne
19
+ * start` is what triggers that build) and the published package ships only
20
+ * `bin` + `scripts` + `agent-assets`. The pure functions here are pinned by a
21
+ * peer `.test.ts` under the daemon/shared `src` tree (the `ports.mjs`
22
+ * precedent), so they run under the standard vitest suite. The win32 read branch
23
+ * carries the usual "no Windows runtime validation" caveat; by construction it
24
+ * can only *degrade* to the legacy bare-PID behavior, never make things worse.
25
+ */
26
+ import { execFileSync } from "node:child_process";
27
+ import { accessSync, constants, readFileSync } from "node:fs";
28
+ import { delimiter, join } from "node:path";
29
+ import process from "node:process";
30
+
31
+ /**
32
+ * Serialize PID metadata to the pidfile.
33
+ *
34
+ * Line 1 is the bare PID so an *older* aitne (which parses only line 1) still
35
+ * reads files written by this version — downgrade-safe. The identity token is
36
+ * written as a trailing `key=value` line (not positional) so a future field can
37
+ * be added without breaking parse order; {@link parsePidMeta} already ignores
38
+ * unknown keys. An absent token is omitted.
39
+ */
40
+ export function serializePidMeta({ pid, startToken = null }) {
41
+ let out = `${pid}\n`;
42
+ if (startToken != null) {
43
+ const token = String(startToken).replace(/[\r\n]+/g, " ").trim();
44
+ if (token.length > 0) out += `start=${token}\n`;
45
+ }
46
+ return out;
47
+ }
48
+
49
+ /**
50
+ * Parse a pidfile written by {@link serializePidMeta} OR a legacy single-line
51
+ * (`<pid>\n`) file. Returns `null` if line 1 is not a finite integer. Unknown
52
+ * trailer keys are ignored, so the format stays forward-compatible.
53
+ */
54
+ export function parsePidMeta(content) {
55
+ if (typeof content !== "string") return null;
56
+ const lines = content.split(/\r?\n/);
57
+ const pid = Number.parseInt((lines[0] ?? "").trim(), 10);
58
+ if (!Number.isFinite(pid)) return null;
59
+ let startToken = null;
60
+ for (const line of lines.slice(1)) {
61
+ const eq = line.indexOf("=");
62
+ if (eq < 0) continue;
63
+ const key = line.slice(0, eq).trim();
64
+ const val = line.slice(eq + 1).trim();
65
+ if (key === "start") startToken = val.length > 0 ? val : null;
66
+ }
67
+ return { pid, startToken };
68
+ }
69
+
70
+ /**
71
+ * Extract field 22 (`starttime`, clock-ticks since boot) from `/proc/<pid>/stat`.
72
+ *
73
+ * The `comm` field (field 2) is wrapped in parens and may itself contain spaces
74
+ * and `)`, so the only safe split point is the LAST `)` — everything after it
75
+ * is space-separated starting at field 3 (state). field 22 is therefore index
76
+ * `22 - 3 = 19` in that tail. Boot-relative, so immune to wall-clock changes;
77
+ * across a reboot the recorded value's epoch is gone, which correctly reads as
78
+ * a mismatch (stale).
79
+ */
80
+ export function parseLinuxStat(statContent) {
81
+ if (typeof statContent !== "string") return null;
82
+ const close = statContent.lastIndexOf(")");
83
+ if (close < 0) return null;
84
+ const fields = statContent.slice(close + 1).trim().split(/\s+/);
85
+ const starttime = fields[19];
86
+ return starttime && /^\d+$/.test(starttime) ? starttime : null;
87
+ }
88
+
89
+ /**
90
+ * Resolve the PowerShell executable on Windows. Prefer Windows PowerShell 5.1
91
+ * (`powershell.exe`), fall back to PowerShell 7+ (`pwsh.exe`) for minimal /
92
+ * Server-Core / pwsh-7-only hosts, else keep the default so a missing host
93
+ * surfaces a clear ENOENT. Mirrors `browser-history/lifecycle/platform.ts`.
94
+ */
95
+ /* c8 ignore start -- win32-only path resolution; the POSIX test runner never enters this */
96
+ function resolveWindowsPowerShell() {
97
+ const pathValue = process.env.PATH ?? "";
98
+ const exts = process.env.PATHEXT?.split(";").filter(Boolean) ?? [".EXE"];
99
+ const probe = (name) => {
100
+ const hasExt = /\.[A-Za-z0-9]+$/.test(name);
101
+ for (const dir of pathValue.split(delimiter)) {
102
+ if (!dir) continue;
103
+ const candidates = hasExt ? [name] : exts.map((e) => `${name}${e}`);
104
+ for (const c of candidates) {
105
+ try {
106
+ accessSync(join(dir, c), constants.X_OK);
107
+ return true;
108
+ } catch {
109
+ // keep scanning
110
+ }
111
+ }
112
+ }
113
+ return false;
114
+ };
115
+ return probe("powershell.exe") ? "powershell.exe" : probe("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
116
+ }
117
+ /* c8 ignore stop */
118
+
119
+ /**
120
+ * Read an opaque, OS-native start-time token for `pid`, or `null` if the PID is
121
+ * gone / the read fails. The token is compared *only* for exact-string equality
122
+ * against a token captured earlier for the same PID on the same machine, so its
123
+ * format is irrelevant as long as it is stable for a given process incarnation.
124
+ *
125
+ * `deps` is for testing — inject `platform` / `execFileSync` / `readFileSync`
126
+ * to exercise a branch off its native OS without touching the real system.
127
+ * Reads use `execFileSync` (never a shell) with a timeout; the PID is numeric,
128
+ * so there is no injection surface, but args stay arrayed on principle.
129
+ */
130
+ export function readProcessStartToken(pid, deps = {}) {
131
+ const platform = deps.platform ?? process.platform;
132
+ const exec = deps.execFileSync ?? execFileSync;
133
+ const readFile = deps.readFileSync ?? readFileSync;
134
+ if (pid == null || !Number.isFinite(Number(pid))) return null;
135
+ try {
136
+ if (platform === "linux") {
137
+ return parseLinuxStat(readFile(`/proc/${pid}/stat`, "utf8"));
138
+ }
139
+ if (platform === "darwin") {
140
+ const out = exec("ps", ["-o", "lstart=", "-p", String(pid)], {
141
+ stdio: ["ignore", "pipe", "ignore"],
142
+ timeout: 5_000,
143
+ windowsHide: true,
144
+ });
145
+ const token = String(out).replace(/[\r\n]+/g, " ").trim();
146
+ return token.length > 0 ? token : null;
147
+ }
148
+ /* c8 ignore start -- win32-only; not reachable from the POSIX test runner */
149
+ if (platform === "win32") {
150
+ const ps = resolveWindowsPowerShell();
151
+ const out = exec(
152
+ ps,
153
+ [
154
+ "-NoProfile",
155
+ "-NonInteractive",
156
+ "-Command",
157
+ `(Get-Process -Id ${Number(pid)} -ErrorAction Stop).StartTime.ToString('o')`,
158
+ ],
159
+ { stdio: ["ignore", "pipe", "ignore"], timeout: 5_000, windowsHide: true },
160
+ );
161
+ const token = String(out).replace(/[\r\n]+/g, " ").trim();
162
+ return token.length > 0 ? token : null;
163
+ }
164
+ /* c8 ignore stop */
165
+ return null;
166
+ } catch {
167
+ return null;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Classify a pidfile's recorded process against the live system.
173
+ *
174
+ * - `stale` — dead, or alive but the start-time token *differs*
175
+ * (PID recycled). Caller should remove the pidfile.
176
+ * - `running-ours` — alive and the start-time token matches.
177
+ * - `running-unverified` — alive but identity can't be confirmed: a legacy file
178
+ * with no token, or the OS start-time read failed.
179
+ * Caller treats this as running (the pre-fix bare-PID
180
+ * behavior), so the change can never regress; a legacy
181
+ * file self-heals on the next `writePid`.
182
+ *
183
+ * `isAlive` / `readToken` are injected so the decision is pure and fully
184
+ * unit-testable without touching real processes.
185
+ */
186
+ export function classifyPid(meta, { readToken, isAlive }) {
187
+ if (meta == null || meta.pid == null) return "stale";
188
+ if (!isAlive(meta.pid)) return "stale";
189
+ if (meta.startToken == null) return "running-unverified";
190
+ const live = readToken(meta.pid);
191
+ if (live == null) return "running-unverified";
192
+ return live === meta.startToken ? "running-ours" : "stale";
193
+ }
@@ -56,7 +56,7 @@ function readDarwin() {
56
56
  return execFileSync(
57
57
  "security",
58
58
  ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-w"],
59
- { encoding: "utf-8" },
59
+ { encoding: "utf-8", timeout: 5_000 },
60
60
  ).trim();
61
61
  } catch {
62
62
  return null;
package/scripts/start.mjs CHANGED
@@ -93,21 +93,31 @@ try {
93
93
  }
94
94
  const nextBin = resolveNextBin(dashboardDir);
95
95
  const dashArgs = nextSpawnArgs(dashboardDir, nextBin, [
96
- "dev", "--port", String(DASHBOARD_PORT),
96
+ // Bind IPv4 loopback explicitly: with no --hostname, Next binds the
97
+ // unspecified IPv6 address `::`, and on Windows (IPV6_V6ONLY on) the
98
+ // 127.0.0.1 readiness probe in bin/aitne.mjs can't reach it. Match the
99
+ // probe and the daemon's 127.0.0.1-only posture. Byte-identical on
100
+ // macOS/Linux where the probe already reaches a `::`-bound socket.
101
+ "dev", "--port", String(DASHBOARD_PORT), "--hostname", "127.0.0.1",
97
102
  ]);
98
- const dashboard = spawn(nextBin, dashArgs, {
103
+ const useShimShell = IS_WINDOWS && nextBin.toLowerCase().endsWith(".cmd");
104
+ const dashCommand = useShimShell ? `"${nextBin}"` : nextBin;
105
+ const dashboard = spawn(dashCommand, dashArgs, {
99
106
  cwd: dashboardDir,
100
107
  env: process.env,
101
108
  stdio: "inherit",
102
109
  windowsHide: true,
103
- shell: IS_WINDOWS && nextBin.toLowerCase().endsWith(".cmd"),
110
+ shell: useShimShell,
104
111
  });
105
112
  children.push(dashboard);
106
113
 
107
114
  // ── 4. Auto-open browser ──
108
115
 
109
116
  if (!noOpen) {
110
- const url = `http://localhost:${DASHBOARD_PORT}`;
117
+ // Match the dashboard's 127.0.0.1-only bind above. Using `localhost` here
118
+ // resolves to `::1` first on Windows, so both the readiness probe AND the
119
+ // browser open would miss the IPv4-only socket and time out.
120
+ const url = `http://127.0.0.1:${DASHBOARD_PORT}`;
111
121
  waitForHttpReady(url, {
112
122
  // `next dev` can take ~30s on a cold boot; give it headroom.
113
123
  timeoutMs: 60_000,
@@ -1,205 +0,0 @@
1
- ---
2
- schema_version: 1
3
- slug: features/routines/hourly-check
4
- title: Hourly Check
5
- id: hourly-check
6
- aliases:
7
- - hourly
8
- - observation consumer
9
- - hourly check gate
10
- category: features
11
- summary: |
12
- A medium-tier routine that fires every hour during active hours,
13
- drains the pending observations queue, and decides whether the
14
- accumulated change pattern warrants surfacing. Runs through a
15
- four-layer gate that keeps quiet days quiet.
16
- section: routines
17
- tags:
18
- - routines
19
- - autonomous
20
- - observations
21
- - polling
22
- - hourly-check
23
- status: stable
24
- ui_anchors:
25
- - /settings/routines
26
- - /settings/schedule
27
- - /activity?tab=system
28
- ask_examples:
29
- - What does the hourly check do?
30
- - When does it run?
31
- - How does the hourly check decide whether to escalate?
32
- - What is the harvest pre-pass?
33
- - How do I tune the gate's freshness window?
34
- locale: en-US
35
- created: 2026-04-25
36
- updated: 2026-05-28
37
- keywords:
38
- - hourly
39
- - observations
40
- - polling
41
- - hourly check
42
- - gate
43
- - layer 1
44
- - layer 2
45
- - layer 3
46
- - harvestForGate
47
- - decideStage
48
- - pre-pass
49
- related:
50
- - concepts/observations
51
- - concepts/routines
52
- - concepts/delegated-mode
53
- - features/integrations/browser-history
54
- process_keys:
55
- - routine.hourly_check
56
- - routine.hourly_check.triage
57
- - routine.fetch_window
58
- config_keys:
59
- - hourlyCheckEnabled
60
- - hourlyCheckIntervalMinutes
61
- - hourlyCheckActiveStartHour
62
- - hourlyCheckActiveEndHour
63
- - hourlyCheckMinObservations
64
- - hourlyCheckStage2Enabled
65
- - hourlyCheckHeartbeatHours
66
- - hourlyCheckLowSignalPendingCeiling
67
- - hourlyCheckPrePassFreshnessMinutes
68
- ---
69
-
70
- # Hourly Check
71
-
72
- A medium-tier routine that drains the observations queue every hour
73
- during active hours and decides whether the accumulated change
74
- pattern warrants notifying you or appending to `state/today.md`. The
75
- "decide" step is a four-layer gate designed to keep quiet days quiet
76
- without missing the moment when something piles up.
77
-
78
- ## When It Runs
79
-
80
- Every `hourlyCheckIntervalMinutes` (default 60), inside the active
81
- window `[hourlyCheckActiveStartHour, hourlyCheckActiveEndHour)`. The
82
- trigger:
83
-
84
- - **Skips** when the morning routine has not yet run today
85
- (recoverable — the dispatcher enqueues a `queueMorningRoutineWake`
86
- self-recovery and the hourly check resumes after the morning
87
- routine succeeds).
88
- - **Skips** when another hourly check is in flight (atomic flag).
89
- - **Skips** when accumulated signal is below
90
- `hourlyCheckMinObservations` and the heartbeat window
91
- (`hourlyCheckHeartbeatHours`) hasn't elapsed.
92
-
93
- Every tick writes one `agent_actions` row with
94
- `action_type = 'hourly_check.gate'` so the gate's decisions are
95
- auditable from `aitne audit` and the dashboard activity feed.
96
-
97
- ## The Four-Layer Gate
98
-
99
- The gate replaced the pre-Phase-9 "30-minute rate-limit" gate. It
100
- runs on every cron tick; only Layer 4 actually spawns a medium-tier
101
- agent session.
102
-
103
- ### Layer 1 — Harvest pre-pass
104
-
105
- `HourlyCheckCoordinator.harvestForGate` spawns a lite-tier
106
- `routine.fetch_window` session for each delegated / native
107
- integration whose `runtime_state.pre_pass_last_run:<key>` is older
108
- than `hourlyCheckPrePassFreshnessMinutes`. The pre-pass fetches the
109
- integration's hourly window (mail / calendar / Notion) and POSTs the
110
- results to `/api/observations`, so the rest of the gate sees a
111
- populated queue instead of stale data.
112
-
113
- Telemetry: `agent_actions.detail.harvest_ran` /
114
- `harvest_integrations` / `harvest_failed_integrations`.
115
-
116
- ### Layer 2 — Signal compute
117
-
118
- `computeHourlyCheckSignals` reads the observation table mode-blind —
119
- it filters by source-prefix sets derived from
120
- `INTEGRATION_DESCRIPTORS`, never by `actor`. Signal categories
121
- include unread-mail / event-change / repo-change / browser-history
122
- clusters; each category produces a (count, last-seen, summary)
123
- tuple. The pre-pass freshness gate from Layer 1 is what guarantees
124
- those counts reflect "now", not "an hour ago".
125
-
126
- ### Layer 3 — Decide stage
127
-
128
- `decideStage` is a pure function over the signal snapshot plus the
129
- per-tick config (heartbeat status, `hourlyCheckStage2Enabled`,
130
- `hourlyCheckLowSignalPendingCeiling`). It returns one of three stages
131
- — `stage0_silent`, `stage2`, or `stage3`:
132
-
133
- | Stage | What happens |
134
- |---|---|
135
- | `stage0_silent` | No agent session. Consume the pending observations and append a single best-effort line to `state/today.md`'s `## Agent Log`. This is the verdict for both "no signals" and "low signal under the ceiling". |
136
- | `stage2` | Only reachable when `hourlyCheckStage2Enabled = true` (default `false`, so low-signal ticks fall through to `stage3`). Runs a lite-tier `routine.hourly_check.triage` call whose strict JSON output decides `log_only` (→ silent path) vs. `escalate` (→ `stage3`). |
137
- | `stage3` | Spawn the full medium-tier `routine.hourly_check` session — the visible agent run. Reached directly on high-novelty signals (VIP mail, calendar conflict, overdue agent plan, approaching schedule) or as the cautious default when Stage 2 is off. |
138
-
139
- When a Layer 1 pre-pass fails (`harvest.failed`), the gate
140
- short-circuits to a cautious `stage3` with reason
141
- `cautious_escalate_prepass_failure` and tags the audit row
142
- `agent_actions.detail.cautious_escalate = true` — a failed fetch
143
- should never silently swallow a tick's worth of signals.
144
-
145
- ### Layer 4 — Dispatch
146
-
147
- When Layer 3 returns `stage0_silent` (or Stage 2 returns
148
- `log_only`), the daemon runs `runSilentHourlyCheckPath` — a
149
- daemon-direct write, no agent. When it returns `stage3` (or Stage 2
150
- returns `escalate`), `enqueueStage3HourlyCheck` puts a
151
- `routine.hourly_check` event on the bus, which becomes the visible
152
- agent session.
153
-
154
- ## What It Outputs
155
-
156
- - A best-effort line in `state/today.md`'s `## Agent Log` — even the
157
- silent path appends one (skipped only if `today.md` is missing or
158
- the write lock is held; the observations are still consumed either
159
- way).
160
- - Notifications when warranted (only on a `stage3` agent session).
161
- - An audit row on every tick:
162
- `agent_actions.action_type = 'hourly_check.gate'`, carrying the
163
- harvest + signal + stage detail.
164
-
165
- ## Configuration
166
-
167
- | Setting | Default | Layer | Notes |
168
- |---|---|---|---|
169
- | `hourlyCheckEnabled` | `true` | trigger | Master kill switch. |
170
- | `hourlyCheckIntervalMinutes` | `60` | trigger | Cron cadence. |
171
- | `hourlyCheckActiveStartHour` | `4` | trigger | Active window start. |
172
- | `hourlyCheckActiveEndHour` | `24` | trigger | End-exclusive; `24` ≡ midnight. |
173
- | `hourlyCheckMinObservations` | `1` | trigger | Minimum pending observations before a non-silent stage dispatches; below this the tick records a `below_threshold` skip. |
174
- | `hourlyCheckPrePassFreshnessMinutes` | `30` | Layer 1 | Range `0`–`240`. Skip a per-integration fetch if its pre-pass ran more recently; `0` fetches every tick, `240` is cost-minimal. |
175
- | `hourlyCheckStage2Enabled` | `false` | Layer 3 | Adds the lite-tier triage call; while `false`, low-signal ticks route straight to `stage3`. |
176
- | `hourlyCheckHeartbeatHours` | `4` | Layer 3 | Range `1`–`48`. Force a non-silent stage at least this often, even on a quiet day. |
177
- | `hourlyCheckLowSignalPendingCeiling` | `0` | Layer 3 | Range `0`–`20`. At or below this pending count, a low-signal tick stays silent. `0` keeps the cautious default (any pending observation escalates). |
178
-
179
- (`hourlyCheckGateMode` was removed in HOURLY_CHECK_GATE_REDESIGN_PLAN
180
- Phase 4 — the gate has a single execution path now.)
181
-
182
- ## When Something Goes Wrong
183
-
184
- - **Skipped because the morning routine hasn't run.** The
185
- dispatcher's `morningRoutineRanToday` gate keys on
186
- `agent_actions.result = 'success'`. The skip reason is
187
- `morning_routine_pending_for_today` and triggers a
188
- `queueMorningRoutineWake` self-recovery. See
189
- [Morning Routine Didn't Run](../../troubleshooting/morning-routine-didnt-run.md).
190
- - **Stage 3 never fires on a quiet day.** Expected.
191
- `hourlyCheckHeartbeatHours` forces a tick occasionally so silence
192
- doesn't last indefinitely.
193
- - **Pre-pass keeps failing for one integration.** Read
194
- `agent_actions.detail.harvest_failed_integrations`; the failing
195
- integration name is recorded. Usually an auth issue — check
196
- `/health.integrationModes` and the integration's settings page.
197
-
198
- ## Related
199
-
200
- - [Observations](../../concepts/observations.md) — what populates the
201
- queue Layer 2 reads.
202
- - [Delegated Mode](../../concepts/delegated-mode.md) — the
203
- delegated / native integrations that participate in Layer 1's
204
- harvest.
205
- - [Routines](../../concepts/routines.md)