@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.
- package/README.md +41 -11
- package/agent-assets/agent-profiles/background-task.md +53 -0
- package/agent-assets/agent-profiles/conversational.md +1 -0
- package/agent-assets/agent-profiles/routine-fetch-window.md +14 -75
- package/agent-assets/agent-profiles/routine.md +1 -1
- package/agent-assets/agents/{hourly-check → activity-scan}/agent.md +22 -17
- package/agent-assets/agents/monthly-review/agent.md +6 -5
- package/agent-assets/docs/concepts/agent-day.md +6 -7
- package/agent-assets/docs/concepts/auth-health.md +23 -20
- package/agent-assets/docs/concepts/backends-and-tiers.md +13 -9
- package/agent-assets/docs/concepts/costs-and-quotas.md +14 -12
- package/agent-assets/docs/concepts/delegated-mode.md +18 -17
- package/agent-assets/docs/concepts/memory-model.md +16 -9
- package/agent-assets/docs/concepts/observations.md +24 -20
- package/agent-assets/docs/concepts/process-keys.md +10 -9
- package/agent-assets/docs/concepts/routines.md +34 -31
- package/agent-assets/docs/concepts/safety-and-execution.md +11 -7
- package/agent-assets/docs/concepts/safety-model.md +39 -25
- package/agent-assets/docs/concepts/skills.md +12 -10
- package/agent-assets/docs/features/integrations/browser-history.md +23 -18
- package/agent-assets/docs/features/integrations/calendar.md +28 -17
- package/agent-assets/docs/features/integrations/git.md +13 -11
- package/agent-assets/docs/features/integrations/github.md +22 -14
- package/agent-assets/docs/features/integrations/mail.md +25 -22
- package/agent-assets/docs/features/integrations/notion.md +35 -11
- package/agent-assets/docs/features/integrations/obsidian.md +8 -8
- package/agent-assets/docs/features/lifestyle/git.md +27 -23
- package/agent-assets/docs/features/lifestyle/reading.md +20 -11
- package/agent-assets/docs/features/lifestyle/receipts.md +11 -10
- package/agent-assets/docs/features/lifestyle/travel-bookings.md +4 -3
- package/agent-assets/docs/features/memory-files/agent-journal.md +53 -26
- package/agent-assets/docs/features/memory-files/agent-lessons.md +178 -0
- package/agent-assets/docs/features/memory-files/projects.md +11 -8
- package/agent-assets/docs/features/memory-files/roadmap.md +17 -14
- package/agent-assets/docs/features/memory-files/schedule.md +6 -3
- package/agent-assets/docs/features/memory-files/today.md +10 -7
- package/agent-assets/docs/features/memory-files/user-profile.md +14 -9
- package/agent-assets/docs/features/messaging/bang-commands.md +21 -6
- package/agent-assets/docs/features/messaging/overview.md +17 -14
- package/agent-assets/docs/features/messaging/telegram.md +10 -9
- package/agent-assets/docs/features/operations/activity-and-conversations.md +6 -5
- package/agent-assets/docs/features/operations/approvals.md +6 -5
- package/agent-assets/docs/features/operations/browser-tasks.md +184 -0
- package/agent-assets/docs/features/operations/cost-tracking.md +11 -1
- package/agent-assets/docs/features/operations/managed-chromium.md +4 -2
- package/agent-assets/docs/features/operations/notifications.md +11 -1
- package/agent-assets/docs/features/operations/quiet-hours.md +41 -11
- package/agent-assets/docs/features/operations/schedule-approaching.md +2 -2
- package/agent-assets/docs/features/routines/activity-scan.md +220 -0
- package/agent-assets/docs/features/routines/custom-routines.md +82 -134
- package/agent-assets/docs/features/routines/evening-review.md +23 -13
- package/agent-assets/docs/features/routines/morning-routine.md +7 -5
- package/agent-assets/docs/features/routines/weekly-review.md +24 -3
- package/agent-assets/docs/features/wiki/commands.md +4 -4
- package/agent-assets/docs/features/wiki/cost-and-approval.md +4 -3
- package/agent-assets/docs/features/wiki/dashboard.md +7 -6
- package/agent-assets/docs/features/wiki/overview.md +3 -3
- package/agent-assets/docs/features/wiki/search.md +5 -5
- package/agent-assets/docs/features/wiki/workspaces.md +2 -2
- package/agent-assets/docs/getting-started/01-what-is-this.md +3 -3
- package/agent-assets/docs/getting-started/02-first-steps.md +5 -3
- package/agent-assets/docs/getting-started/04-first-day.md +27 -30
- package/agent-assets/docs/glossary.md +8 -8
- package/agent-assets/docs/guides/add-a-custom-routine.md +122 -68
- package/agent-assets/docs/guides/budget-and-cost-for-wiki.md +2 -2
- package/agent-assets/docs/guides/connect-a-new-mail-account.md +4 -2
- package/agent-assets/docs/guides/explore-with-trace-and-connect.md +5 -4
- package/agent-assets/docs/guides/install-and-run.md +2 -2
- package/agent-assets/docs/guides/maintain-wiki-health.md +2 -2
- package/agent-assets/docs/guides/pause-the-agent.md +27 -21
- package/agent-assets/docs/guides/reinstall-cleanly.md +2 -0
- package/agent-assets/docs/guides/setup-wizard.md +12 -6
- package/agent-assets/docs/guides/use-an-existing-obsidian-vault.md +6 -6
- package/agent-assets/docs/reference/api.md +26 -5
- package/agent-assets/docs/reference/cli-commands.md +3 -3
- package/agent-assets/docs/reference/config.md +51 -24
- package/agent-assets/docs/reference/disallowed-tools.md +6 -4
- package/agent-assets/docs/reference/keyboard-shortcuts.md +2 -2
- package/agent-assets/docs/reference/knowledge-layout.md +25 -12
- package/agent-assets/docs/reference/process-keys.md +9 -9
- package/agent-assets/docs/reference/skills.md +10 -6
- package/agent-assets/docs/troubleshooting/auth-failed.md +9 -8
- package/agent-assets/docs/troubleshooting/dashboard-shows-degraded.md +16 -9
- package/agent-assets/docs/troubleshooting/messaging-not-pairing.md +2 -2
- package/agent-assets/docs/troubleshooting/morning-routine-didnt-run.md +32 -16
- package/agent-assets/docs/troubleshooting/observation-not-detected.md +26 -24
- package/agent-assets/docs/troubleshooting/quota-exhausted.md +7 -6
- package/agent-assets/docs/troubleshooting/wiki-write-failed.md +3 -3
- package/agent-assets/skills/agent-actions/SKILL.md +23 -39
- package/agent-assets/skills/agent-create/SKILL.md +26 -6
- package/agent-assets/skills/attach/SKILL.md +8 -27
- package/agent-assets/skills/background-task/SKILL.md +184 -0
- package/agent-assets/skills/background-task-reply/SKILL.md +100 -0
- package/agent-assets/skills/browser-history/SKILL.md +60 -29
- package/agent-assets/skills/browser-history-respond/SKILL.md +6 -1
- package/agent-assets/skills/browser-task/SKILL.md +33 -31
- package/agent-assets/skills/context/SKILL.md +26 -34
- package/agent-assets/skills/context/curation.json +12 -12
- package/agent-assets/skills/context/references/api.md +22 -20
- package/agent-assets/skills/context/references/required-frontmatter.md +10 -9
- package/agent-assets/skills/context/references/snapshot-files.md +16 -15
- package/agent-assets/skills/context/seeds/file-responsibilities.seed.json +5 -5
- package/agent-assets/skills/context/seeds/frontmatter-requirements.seed.json +3 -3
- package/agent-assets/skills/docs-search/SKILL.md +19 -31
- package/agent-assets/skills/external-services/SKILL.delegated.claude.md +8 -95
- package/agent-assets/skills/external-services/SKILL.delegated.codex.md +8 -94
- package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +8 -94
- package/agent-assets/skills/external-services/SKILL.native.claude.md +15 -9
- package/agent-assets/skills/external-services/SKILL.native.codex.md +11 -5
- package/agent-assets/skills/external-services/SKILL.native.gemini.md +11 -5
- package/agent-assets/skills/external-services/references/exec-errors.md +32 -0
- package/agent-assets/skills/external-services/references/skills-crud.md +5 -5
- package/agent-assets/skills/gmail-lifestyle/SKILL.md +3 -2
- package/agent-assets/skills/gmail-lifestyle/references/receipts-api.md +4 -0
- package/agent-assets/skills/gmail-lifestyle/references/travel-bookings-api.md +9 -0
- package/agent-assets/skills/mail/SKILL.delegated.claude.md +13 -25
- package/agent-assets/skills/mail/SKILL.delegated.codex.md +3 -2
- package/agent-assets/skills/mail/SKILL.delegated.gemini.md +3 -2
- package/agent-assets/skills/mail/SKILL.md +12 -20
- package/agent-assets/skills/mail/SKILL.native.claude.md +24 -16
- package/agent-assets/skills/mail/SKILL.native.codex.md +16 -9
- package/agent-assets/skills/mail/SKILL.native.gemini.md +12 -6
- package/agent-assets/skills/mail/references/api.md +6 -1
- package/agent-assets/skills/mail/references/examples.md +2 -1
- package/agent-assets/skills/managed-tasks/SKILL.md +44 -77
- package/agent-assets/skills/managed-tasks/references/errors.md +25 -14
- package/agent-assets/skills/managed-tasks/references/output-path.md +33 -17
- package/agent-assets/skills/managed-tasks/references/recurrence-rule.md +26 -16
- package/agent-assets/skills/management-policy/SKILL.md +36 -28
- package/agent-assets/skills/management-policy/curation.json +1 -1
- package/agent-assets/skills/management-policy/references/policy-workflow.md +30 -18
- package/agent-assets/skills/notify/SKILL.md +16 -13
- package/agent-assets/skills/notify/references/priority.md +42 -26
- package/agent-assets/skills/notion/SKILL.delegated.claude.md +1 -1
- package/agent-assets/skills/notion/SKILL.delegated.codex.md +1 -1
- package/agent-assets/skills/notion/SKILL.delegated.gemini.md +1 -1
- package/agent-assets/skills/notion/SKILL.md +18 -18
- package/agent-assets/skills/notion/SKILL.native.claude.md +4 -4
- package/agent-assets/skills/notion/SKILL.native.codex.md +3 -3
- package/agent-assets/skills/notion/SKILL.native.gemini.md +3 -3
- package/agent-assets/skills/observations/SKILL.md +9 -24
- package/agent-assets/skills/observations/references/fetch-fallback.md +22 -0
- package/agent-assets/skills/project-doc/SKILL.md +9 -6
- package/agent-assets/skills/project-doc/curation.json +3 -3
- package/agent-assets/skills/project-doc/seeds/project-shape.seed.json +2 -2
- package/agent-assets/skills/project-doc/seeds/slug-grammar.seed.json +3 -3
- package/agent-assets/skills/reading/SKILL.md +8 -42
- package/agent-assets/skills/reading/references/reading-taste.md +5 -5
- package/agent-assets/skills/roadmap/SKILL.md +3 -19
- package/agent-assets/skills/roadmap/references/api.md +23 -8
- package/agent-assets/skills/roadmap/references/horizon-tags.md +11 -0
- package/agent-assets/skills/roadmap/references/migration.md +8 -6
- package/agent-assets/skills/roadmap/references/retention.md +18 -0
- package/agent-assets/skills/schedule/SKILL.md +20 -28
- package/agent-assets/skills/schedule/references/importance.md +23 -0
- package/agent-assets/skills/schedule/references/recurrence-rule.md +26 -16
- package/agent-assets/skills/scheduled-managed-task/SKILL.md +46 -46
- package/agent-assets/skills/today/SKILL.md +38 -81
- package/agent-assets/skills/today/references/agent-plan-lifecycle.md +9 -4
- package/agent-assets/skills/today/references/agent-plan-revision.md +28 -0
- package/agent-assets/skills/today/references/today-skeleton.md +66 -0
- package/agent-assets/skills/today/seeds/agent-notes-flavors.seed.json +1 -1
- package/agent-assets/skills/today/seeds/section-shape.seed.json +6 -6
- package/agent-assets/skills/user-interview/SKILL.md +15 -90
- package/agent-assets/skills/user-interview/references/op-briefing.md +1 -1
- package/agent-assets/skills/user-interview/references/op-dm-handler.md +88 -0
- package/agent-assets/skills/user-interview/references/op-morning.md +2 -2
- package/agent-assets/skills/user-interview/references/sweep-and-fallback.md +1 -1
- package/agent-assets/skills/user-profile/SKILL.md +16 -26
- package/agent-assets/skills/user-profile/curation.json +3 -3
- package/agent-assets/skills/user-profile/references/character-preferences.md +3 -3
- package/agent-assets/skills/wiki/wiki-ask/SKILL.md +1 -1
- package/agent-assets/skills/wiki/wiki-compile/SKILL.md +5 -4
- package/agent-assets/skills/wiki/wiki-connect/SKILL.md +32 -5
- package/agent-assets/skills/wiki/wiki-ingest/SKILL.md +6 -50
- package/agent-assets/skills/wiki/wiki-ingest/references/curl-errors.md +58 -0
- package/agent-assets/skills/wiki/wiki-lint/SKILL.md +20 -14
- package/agent-assets/skills/wiki/wiki-trace/SKILL.md +10 -5
- package/agent-assets/skills/wiki/wiki-vault-rules/SKILL.md +2 -0
- package/agent-assets/system-prompts/routine-research-cluster-update.md +71 -0
- package/agent-assets/task-flows/_partials/feedback-capture.md +30 -0
- package/agent-assets/task-flows/_partials/notion-acquire.notion.md +47 -21
- package/agent-assets/task-flows/background_task.md +81 -0
- package/agent-assets/task-flows/git.local_ahead.stale.md +1 -1
- package/agent-assets/task-flows/git.push.detected.md +1 -1
- package/agent-assets/task-flows/git.tag.created.md +1 -1
- package/agent-assets/task-flows/github.assigned.md +1 -1
- package/agent-assets/task-flows/github.pull_request.review_requested.md +2 -2
- package/agent-assets/task-flows/github.security_alert.md +1 -1
- package/agent-assets/task-flows/message.received.dm.md +11 -3
- package/agent-assets/task-flows/message.received.dm_first.md +8 -2
- package/agent-assets/task-flows/{routine.hourly_check.md → routine.activity_scan.md} +31 -23
- package/agent-assets/task-flows/{routine.hourly_check.triage.md → routine.activity_scan.triage.md} +3 -3
- package/agent-assets/task-flows/routine.evening_review.md +80 -0
- package/agent-assets/task-flows/routine.monthly_review.md +81 -8
- package/agent-assets/task-flows/routine.research_cluster_update.md +33 -19
- package/agent-assets/task-flows/routine.roadmap_refresh.md +2 -2
- package/agent-assets/task-flows/routine.today_refresh.md +1 -1
- package/agent-assets/task-flows/routine.weekly_review.md +124 -4
- package/agent-assets/task-flows/schedule.approaching.md +2 -2
- package/agent-assets/task-flows/scheduled.dm.md +77 -1
- package/agent-assets/task-flows/scheduled.task.md +7 -1
- package/agent-assets/task-flows/wiki.trace.md +1 -1
- package/agent-assets/templates/_manifest.json +2 -2
- package/agent-assets/templates/knowledge/dossiers/_index.md +1 -1
- package/agent-assets/templates/knowledge/dossiers/{hourly.md → activity-scan.md} +1 -1
- package/agent-assets/templates/policies/journal-format.md +1 -1
- package/agent-assets/templates/policies/mcp.md +1 -1
- package/agent-assets/templates/policies/routines/_index.md +1 -1
- package/agent-assets/templates/policies/routines/{hourly.md → activity-scan.md} +5 -5
- package/bin/aitne.mjs +45 -11
- package/package.json +6 -5
- package/scripts/commands/doctor.mjs +11 -2
- package/scripts/lib/process-identity.d.mts +46 -0
- package/scripts/lib/process-identity.mjs +193 -0
- package/scripts/lib/read-api-token.mjs +1 -1
- package/scripts/start.mjs +14 -4
- 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
|
+
}
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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)
|