@kingcrab/pi-imessage 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- package/dist/agent/behavior.js +7 -13
- package/dist/agent/chats.js +7 -8
- package/dist/agent/commands.js +16 -11
- package/dist/agent/harness.js +6 -0
- package/dist/agent/progress.js +30 -4
- package/dist/agent/prompt.js +41 -24
- package/dist/agent/run.js +5 -4
- package/dist/agent/scheduling.js +11 -34
- package/dist/cli.js +42 -5
- package/dist/config/settings.js +4 -0
- package/dist/extensions/browser.js +7 -3
- package/dist/extensions/codemode.js +16 -0
- package/dist/extensions/final-text.js +12 -0
- package/dist/extensions/schedules.js +1 -1
- package/dist/extensions/subagent.js +3 -12
- package/dist/extensions/workspace.js +6 -2
- package/dist/main.js +42 -49
- package/dist/transport/watch.js +7 -1
- package/dist/web/app.js +63 -121
- package/dist/web/server.js +7 -14
- package/dist/web/style.css +0 -6
- package/dist/web/tasks.js +12 -120
- package/docs/adr/{0004-native-extensions.md → 0003-native-extensions.md} +1 -1
- package/docs/adr/{0006-jsonl-storage.md → 0004-jsonl-storage.md} +1 -1
- package/docs/adr/{0007-images-by-path.md → 0005-images-by-path.md} +1 -1
- package/docs/adr/{0010-test-budget.md → 0006-test-budget.md} +1 -1
- package/docs/adr/{0011-reconcile-unsent-answers.md → 0007-reconcile-unsent-answers.md} +2 -2
- package/docs/adr/{0012-run-in-owned-conversation.md → 0008-run-in-owned-conversation.md} +5 -5
- package/docs/adr/{0032-native-background-schedules.md → 0009-native-background-schedules.md} +1 -1
- package/docs/adr/0010-framework-and-workspace.md +27 -0
- package/docs/adr/README.md +24 -48
- package/docs/api.md +16 -8
- package/docs/architecture.md +19 -8
- package/docs/browser.md +7 -7
- package/docs/install.md +109 -0
- package/docs/workspace-extensions.md +8 -4
- package/package.json +4 -5
- package/dist/agent/compaction.js +0 -51
- package/dist/agent/error-log.js +0 -37
- package/dist/extensions/restart.js +0 -100
- package/docs/adr/0003-replies-at-most-once.md +0 -11
- package/docs/adr/0005-history-migration.md +0 -11
- package/docs/adr/0008-compaction-and-run.md +0 -11
- package/docs/adr/0009-one-shot-cutover.md +0 -11
- package/docs/adr/0013-thin-adapters-around-durable.md +0 -18
- package/docs/adr/0014-ephemeral-private-storage.md +0 -13
- package/docs/adr/0015-ordinary-stop-keeps-native-inbox.md +0 -21
- package/docs/adr/0016-process-http-transport.md +0 -21
- package/docs/adr/0017-browser-cli-owned-scope.md +0 -22
- package/docs/adr/0018-tasks-without-prompt-api.md +0 -19
- package/docs/adr/0019-remove-scheduled-services.md +0 -41
- package/docs/adr/0020-chat-task-progress.md +0 -41
- package/docs/adr/0021-readable-progress-without-link-previews.md +0 -33
- package/docs/adr/0022-progress-admission-and-settlement.md +0 -35
- package/docs/adr/0023-retain-progress-type-prefixes.md +0 -18
- package/docs/adr/0024-status-marker-before-type-prefix.md +0 -13
- package/docs/adr/0025-reusable-progress-and-command-preview.md +0 -49
- package/docs/adr/0026-inline-command-only-requests.md +0 -16
- package/docs/adr/0027-command-preview-width.md +0 -10
- package/docs/adr/0028-command-preview-120.md +0 -8
- package/docs/adr/0030-shared-web-read-cache.md +0 -33
- package/docs/adr/0031-quiet-compaction-progress.md +0 -32
- package/docs/adr/0033-operator-scheduled-run.md +0 -23
- package/docs/adr/0034-machine-local-schedule-time.md +0 -36
- package/docs/adr/0035-external-business-task-extensions.md +0 -56
- package/docs/adr/0036-workspace-policy-and-neutral-service-id.md +0 -37
- package/docs/adr/0037-restart-during-in-flight-work.md +0 -30
- package/docs/adr/0038-restart-any-time.md +0 -25
- package/docs/adr/0039-rerun-interrupted-tools.md +0 -24
- package/docs/adr/0040-chat-progress-notices.md +0 -44
- package/docs/adr/0041-restart-and-recovery.md +0 -28
- package/docs/adr/0042-background-schedules-and-workspace-extensions.md +0 -44
- package/docs/adr/0043-remove-legacy-migration.md +0 -23
- package/docs/adr/0044-message-channels.md +0 -32
- package/docs/adr/0045-chat-scheduling-tools.md +0 -51
- package/docs/adr/0046-selective-command-redaction.md +0 -31
- package/docs/adr/0047-one-scheduling-path.md +0 -158
- package/docs/adr/0048-quiet-attachment-reads.md +0 -11
- package/docs/adr/0049-per-chat-progress-notices.md +0 -21
- package/docs/adr/0052-host-owned-self-restart.md +0 -53
- package/docs/adr/0053-service-log-errors.md +0 -44
- package/ops/README.md +0 -250
- package/ops/preflight.mjs +0 -28
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# 0046. Selective command-preview redaction
|
|
2
|
-
|
|
3
|
-
Status: proposed (owner requested `***` value masking on 2026-10-06; implementation tracked in #36)
|
|
4
|
-
|
|
5
|
-
Supersedes only ADR 0040's whole-command credential suppression rule.
|
|
6
|
-
|
|
7
|
-
**Problem.** A mention of `token`, `password`, `.env` or `auth.json` hides the
|
|
8
|
-
entire command, even when it is just a source search or filename.
|
|
9
|
-
|
|
10
|
-
**Example.** `TOKEN=demo123 curl --retry 3` becomes
|
|
11
|
-
`→ [tool] bash: TOKEN=*** curl --retry 3`. `rg 'token' src` remains readable.
|
|
12
|
-
|
|
13
|
-
**Decision.** Replace recognized credential values with `***`, retaining the
|
|
14
|
-
surrounding command instead of a hidden-command placeholder. Cover sensitive
|
|
15
|
-
assignments, options, authorization headers, URL userinfo, structured values,
|
|
16
|
-
known credential prefixes and PEM private-key blocks. Read complete shell words,
|
|
17
|
-
including quoted fragments, escaped spaces and command substitutions. Opaque
|
|
18
|
-
escaped JSON and ambiguous substitutions may lose additional argument text to
|
|
19
|
-
avoid showing a partial credential. Filenames and keyword mentions alone do not
|
|
20
|
-
trigger hiding. Screening remains best effort, not an arbitrary-secret guarantee.
|
|
21
|
-
|
|
22
|
-
Apply masking to the full command before whitespace normalization, URL/domain
|
|
23
|
-
defanging and the existing 120-Unicode-code-point bound. Native execution,
|
|
24
|
-
non-bash summaries, progress ownership, settlement and durable receipts remain
|
|
25
|
-
unchanged. No new scheduler, pipeline, tool execution or delivery replay.
|
|
26
|
-
|
|
27
|
-
**Verification.** Extend the existing isolated messaging regression with exact
|
|
28
|
-
preview examples, quote/escape cases, structured credentials, line continuations,
|
|
29
|
-
substitutions, known prefixes, private keys, keyword-only searches and masking
|
|
30
|
-
before truncation. Existing native execution, ownership and uncertain-send
|
|
31
|
-
regressions remain in force.
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# 0047. One scheduling path for recurring and one-off work
|
|
2
|
-
|
|
3
|
-
Status: accepted (owner requested the minimal implementation on 2026-10-06)
|
|
4
|
-
|
|
5
|
-
## Problem
|
|
6
|
-
|
|
7
|
-
Recurring workspace jobs currently enter through a native scheduler task, while
|
|
8
|
-
`serve --run-scheduled JOB --request-id ID` calls `runOnce()` and creates an
|
|
9
|
-
occurrence directly. Both use Durable, but admission follows two paths, and a
|
|
10
|
-
manual trial requires a service restart.
|
|
11
|
-
|
|
12
|
-
Calling all work "scheduled" is not sufficient. The common path must own deadline
|
|
13
|
-
persistence, deduplication and non-overlap. A trial must also retain the original
|
|
14
|
-
job's business state, rather than create an empty English learning history or
|
|
15
|
-
bypass the monthly quota.
|
|
16
|
-
|
|
17
|
-
## Example
|
|
18
|
-
|
|
19
|
-
Workplace English has a daily 07:45 trigger. An operator requests one trial now.
|
|
20
|
-
The trial receives its own stable trigger ID, but targets the existing English
|
|
21
|
-
job and business conversation. Reloading twice does not run it twice, and the
|
|
22
|
-
regular 07:45 deadline does not move. Existing per-date card deduplication still
|
|
23
|
-
applies: a trial is not permission to resend today's card.
|
|
24
|
-
|
|
25
|
-
## Evaluation
|
|
26
|
-
|
|
27
|
-
- **One common scheduling path:** recommended. Recurrence and an immediate trial
|
|
28
|
-
differ in their deadlines and repetition, not their executor or delivery path.
|
|
29
|
-
- **One physical configuration file:** unnecessary. Keep trusted business code
|
|
30
|
-
and configuration in the workspace extensions required by ADR 0042. Their
|
|
31
|
-
declarations pass through one framework admission function.
|
|
32
|
-
- **Automatic configuration polling:** unnecessary for the first version. The
|
|
33
|
-
existing `reload_extensions` tool and `/reload` already apply configuration
|
|
34
|
-
through the owning service without restart. Polling would add partial-write,
|
|
35
|
-
repeated-load and failure-handling concerns without helping deadline execution.
|
|
36
|
-
- **A local CLI/socket or HTTP run API:** unnecessary here. A trial can be a
|
|
37
|
-
one-off declaration followed by the existing reload. This trades one explicit
|
|
38
|
-
reload operation for avoiding another control protocol.
|
|
39
|
-
- **A separate business executor for every cadence:** rejected. Reminder,
|
|
40
|
-
script and model workflows remain native business tasks; they do not become
|
|
41
|
-
separate schedulers.
|
|
42
|
-
|
|
43
|
-
## Decision
|
|
44
|
-
|
|
45
|
-
### One admission and execution path
|
|
46
|
-
|
|
47
|
-
```text
|
|
48
|
-
Workspace extension configuration
|
|
49
|
-
|
|
|
50
|
-
startup / reload_extensions / /reload
|
|
51
|
-
|
|
|
52
|
-
validate and register triggers
|
|
53
|
-
|
|
|
54
|
-
native background Scheduler task
|
|
55
|
-
|
|
|
56
|
-
sleep(persisted absolute deadline)
|
|
57
|
-
|
|
|
58
|
-
native occurrence wrapper
|
|
59
|
-
|
|
|
60
|
-
registered native business task
|
|
61
|
-
|
|
|
62
|
-
native receipts -> read-only Scheduled Web
|
|
63
|
-
|
|
|
64
|
-
optional outbox -> existing send receipts
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The reload commands are configuration-loading entry points, not direct execution
|
|
68
|
-
paths. No file edit alone invokes business work. An immediate trial becomes due
|
|
69
|
-
once its declaration has been accepted; it is not a promise of zero latency.
|
|
70
|
-
The service must be running, and publication of validated code/configuration must
|
|
71
|
-
complete before newly due work can execute under that definition.
|
|
72
|
-
|
|
73
|
-
The framework retains one owning Harness. Do not introduce another daemon,
|
|
74
|
-
operating-system timer, SDK scheduler bridge, database, or polling watcher.
|
|
75
|
-
|
|
76
|
-
### Job identity and trigger identity
|
|
77
|
-
|
|
78
|
-
- A **job** names registered business code, input, destination and its existing
|
|
79
|
-
business conversation/state. It is not an arbitrary command or prompt supplied
|
|
80
|
-
by a new public execution API.
|
|
81
|
-
- A **trigger** names when that job is eligible to run. Recurring triggers and
|
|
82
|
-
one-off triggers use the same registration function and scheduler definition.
|
|
83
|
-
- The minimal version preserves elapsed-time intervals and one local daily slot,
|
|
84
|
-
and adds a one-off absolute deadline or `now`. Multiple daily slots and monthly
|
|
85
|
-
calendars are deferred, not part of this first change. Do not add a cron parser.
|
|
86
|
-
- Resolve `now` to an absolute timestamp once, in the admission transaction.
|
|
87
|
-
Persist the stable trigger ID, target job and deadline together. Subsequent
|
|
88
|
-
reloads/restarts recover that trigger, including a terminal one; they never
|
|
89
|
-
reevaluate `now`. A new intended trial needs a new trigger ID. Conflicting reuse
|
|
90
|
-
of a one-off ID fails explicitly.
|
|
91
|
-
- A one-off declaration targets an existing registered job. Do not give it a
|
|
92
|
-
fresh business conversation, reset learning/checkpoints, or change the regular
|
|
93
|
-
trigger's deadline. A workspace extension must provide this declarative trial
|
|
94
|
-
binding; trial requests do not require generating a new extension per run.
|
|
95
|
-
- Enforce non-overlap by target job, not merely by trigger. A due one-off can wait
|
|
96
|
-
for that job's active occurrence, then admit its own occurrence. Do not report
|
|
97
|
-
another trigger's work as completion of the new request.
|
|
98
|
-
- Commit occurrence admission and the scheduler checkpoint together. A one-off
|
|
99
|
-
scheduler becomes terminal after its occurrence; a recurring scheduler saves
|
|
100
|
-
the next deadline. Failures remain visible in native occurrence records.
|
|
101
|
-
|
|
102
|
-
### Recovery, reload and delivery
|
|
103
|
-
|
|
104
|
-
- Preserve ADR 0042's machine-local calendar/DST rules, saved absolute deadlines,
|
|
105
|
-
daily/interval overdue behavior and lack of historical backfill. A one-off
|
|
106
|
-
admitted before shutdown runs once when overdue after restart.
|
|
107
|
-
- Load and validate a complete candidate before accepting declarations. A failed
|
|
108
|
-
reload leaves old definitions and schedules active and must not start a trial.
|
|
109
|
-
Reload does not interrupt existing business calls. Pausing/removing a declaration
|
|
110
|
-
prevents future admission, not undoing already admitted effects.
|
|
111
|
-
- Use native task versions/migrations where persisted structures change. Preserve
|
|
112
|
-
existing job/conversation/task IDs, learning state, checkpoints, execution
|
|
113
|
-
history and delivery receipts. Do not recreate terminal trials on startup.
|
|
114
|
-
- Business tasks keep their own replay and success policy under ADR 0041. A shared
|
|
115
|
-
clock does not make a script's external effects transactional or replay-safe.
|
|
116
|
-
Reflection advances its checkpoint and starts backup only after verified success.
|
|
117
|
-
- Keep the existing outgoing queue and at-most-once transport receipts. An
|
|
118
|
-
interrupted send becomes `unknown` and is not automatically resent. One-off
|
|
119
|
-
execution does not override a business quota, per-date dedupe or a send receipt.
|
|
120
|
-
- Reuse the compact Scheduled page and native occurrence history. Show one-off
|
|
121
|
-
executions under their target job, with links to their Task tree. Viewing the
|
|
122
|
-
page starts no work; add no execution buttons.
|
|
123
|
-
|
|
124
|
-
### Entry points removed or deferred
|
|
125
|
-
|
|
126
|
-
After the unified one-off path is available, remove the operator startup-run
|
|
127
|
-
flags and the direct-occurrence `runOnce()` bypass. Keep their historical request
|
|
128
|
-
and execution records; do not replay them during migration. Do not add a CLI or
|
|
129
|
-
Unix socket replacement.
|
|
130
|
-
|
|
131
|
-
ADR 0045 remains a separate proposal about chat scheduling tools. This ADR does
|
|
132
|
-
not enable or reject those tools. If accepted, their declarations must use this
|
|
133
|
-
same admission/clock/occurrence path, preserve chat ownership boundaries, and
|
|
134
|
-
must not introduce another scheduler or call a business executor directly.
|
|
135
|
-
|
|
136
|
-
## Scope and consequences
|
|
137
|
-
|
|
138
|
-
Framework changes concern the schedule declaration contract, common admission,
|
|
139
|
-
calendar calculation, scheduler lifecycle and removal of the operator bypass.
|
|
140
|
-
Personal messages, weather/news logic, memory updates, photo-date checks and
|
|
141
|
-
Reflection remain workspace extensions, not framework-specific branches.
|
|
142
|
-
|
|
143
|
-
Restoring the selected legacy jobs uses this path. Stop only their competing old
|
|
144
|
-
cron producers during the authorized cutover; do not disable unrelated system
|
|
145
|
-
jobs. Fixed-message business tasks do not restore the retired reminder service,
|
|
146
|
-
PT workers or HTTP `/prompt` / `/send`. No restoration or migration is performed
|
|
147
|
-
by accepting a file reload alone
|
|
148
|
-
unless those jobs are explicitly declared.
|
|
149
|
-
|
|
150
|
-
The first version requires an explicit reload after editing configuration. This
|
|
151
|
-
is intentional: it avoids a new watcher and a new management service. Persistence
|
|
152
|
-
cannot wake a stopped process or machine. Exactly-once external delivery is not
|
|
153
|
-
claimed.
|
|
154
|
-
|
|
155
|
-
This supersedes only ADR 0042's operator-run clause (which
|
|
156
|
-
consolidated ADR 0033), and extends its cadence/declaration contract. Its workspace
|
|
157
|
-
ownership, recovery and delivery rules remain in force. Accepted ADRs stay
|
|
158
|
-
unchanged.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 0048. Quiet attachment reads
|
|
2
|
-
|
|
3
|
-
Status: proposed (owner-requested behavior in #38, 2026-10-07)
|
|
4
|
-
|
|
5
|
-
**Problem.** Reading an archived attachment adds noisy tool notices containing long private archive paths.
|
|
6
|
-
|
|
7
|
-
**Example.** A native `read` of `WORKING_DIR/attachments/<chat>/<file>` produces no request or completion notice. An ordinary file read still shows the existing progress pair.
|
|
8
|
-
|
|
9
|
-
**Decision.** Add a narrow exception to ADR 0040's all-tool progress rule: suppress both admission and settlement notices for native `read` calls under the active workspace attachment root, including owned child calls and failed reads. Normalize relative, home, @ and file-URL paths and resolve filesystem aliases using metadata only. Match directory boundaries, not a generic attachment substring. Other reads, tools, task progress, manual compaction and final replies remain unchanged.
|
|
10
|
-
|
|
11
|
-
The existing native observer filters before assigning notice routing or making direct-send receipts. Execution, result/error history, receipt/no-replay rules and the single pipeline are unchanged. No new poller, sender, permission boundary, data removal or credential policy is introduced.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# 0049. Per-chat progress notices
|
|
2
|
-
|
|
3
|
-
Status: proposed (owner-requested implementation, 2026-10-24)
|
|
4
|
-
|
|
5
|
-
**Problem.** `progressMessages` in `settings.json` turns tool progress notices on or off for every chat at once. A busy group wants only the final messages while an admin chat wants to watch the tools.
|
|
6
|
-
|
|
7
|
-
**Example.**
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
/verbose -> Verbose: on (default)
|
|
11
|
-
/verbose off -> Verbose: off (this chat only)
|
|
12
|
-
/verbose default -> Verbose: on (default)
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
**Decision.**
|
|
16
|
-
|
|
17
|
-
- Each chat record in the native `imessage.chats` document gets an optional `progress` boolean. `/verbose on|off` sets it and `/verbose default` removes it; `/verbose` alone reports the effective state. The record version is unchanged; old records have no field.
|
|
18
|
-
- The progress observer (ADR 0040) resolves the owning chat first and then gates both the admission and the settlement notice by `chat.progress ?? settings.progressMessages`. Reply-enabled allowlisting, formatting, screening, receipts and no-replay rules are unchanged.
|
|
19
|
-
- `settings.json` stays the default and is never rewritten by a command; the per-chat choice lives only in Durable.
|
|
20
|
-
|
|
21
|
-
**Consequences.** A `/verbose off` chat still runs tools and records their results; it only stops seeing the two notice lines. Child notices follow the owning chat's choice. Changing the global default does not touch chats with an explicit choice.
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# 0052. The host owns its own restart
|
|
2
|
-
|
|
3
|
-
Status: accepted (owner accepted on 2026-10-24)
|
|
4
|
-
|
|
5
|
-
**Problem.** A chat assistant restarts the service by writing a controller
|
|
6
|
-
script and running it from a `bash` tool call. The host cannot tell that this
|
|
7
|
-
call is about to stop it. When the call is still running at the stop, ADR 0041
|
|
8
|
-
reruns it on the next start, which launches another controller and stops the
|
|
9
|
-
service again (#39: about 70 restarts in eleven minutes). A host-side stop
|
|
10
|
-
heuristic that aborts a tool interrupted twice in a row was tried the same
|
|
11
|
-
day; it limits the damage after the fact, does not remove the cause, and
|
|
12
|
-
aborts any long tool that two deployments happen to interrupt in a row.
|
|
13
|
-
|
|
14
|
-
**Example.**
|
|
15
|
-
|
|
16
|
-
```text
|
|
17
|
-
restart_service({ release: "d627232…" })
|
|
18
|
-
-> Restart into /Users/clawbot/pi-imessage-releases/d627232… is scheduled;
|
|
19
|
-
the service stops after this call settles. Controller directory:
|
|
20
|
-
/tmp/pi-imessage-restart.Xy12ab. Verify in a later call.
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
The call settles, the host opens the controller in Terminal, the controller
|
|
24
|
-
stops the old process and becomes the new one. The next turn verifies the
|
|
25
|
-
listener, the log and the running release directory.
|
|
26
|
-
|
|
27
|
-
**Decision.**
|
|
28
|
-
|
|
29
|
-
- A built-in `restart_service` tool takes the full commit hash of a prepared
|
|
30
|
-
release directory, a sibling of the running one. It refuses a hash that is not
|
|
31
|
-
a directory with `src/cli.ts`, `node_modules` and `ops/restart.command`, and
|
|
32
|
-
a second request while one is scheduled.
|
|
33
|
-
- The tool writes the private controller directory of ops/README.md step 3
|
|
34
|
-
itself: `environment.sh` from the process environment, `control.sh` from the
|
|
35
|
-
process's own PID, start time and command line, and a copy of
|
|
36
|
-
`ops/restart.command` from the target release. The controller deletes
|
|
37
|
-
`environment.sh` after sourcing it.
|
|
38
|
-
- The tool returns at once. The host launches the controller only after the
|
|
39
|
-
tool task has settled, so the stop can never interrupt the call that asked
|
|
40
|
-
for it.
|
|
41
|
-
- The tool is `replay: "unsafe"`. If it is interrupted anyway, the model gets
|
|
42
|
-
an interrupted result and checks what is running instead of a rerun
|
|
43
|
-
launching a second controller.
|
|
44
|
-
- Ordinary tools keep the rerun default of ADR 0041. Operators keep the manual
|
|
45
|
-
procedure for a cold start when no service is running.
|
|
46
|
-
|
|
47
|
-
**Consequences.** A self-restart costs one restart and no extra model turn.
|
|
48
|
-
The stop still happens at a tool boundary of the requesting chat, so that chat's
|
|
49
|
-
next model request is repeated after the restart, as ADR 0041 accepts. The
|
|
50
|
-
restart loop of #39 cannot recur through this tool, so the stop heuristic is
|
|
51
|
-
removed and ADR 0041 applies unchanged to every other tool. A chat that writes
|
|
52
|
-
its own controller through `bash` is still subject to the ops rule of ending
|
|
53
|
-
the call right after `open`.
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# 0053. Service-log errors for failures that break normal operation
|
|
2
|
-
|
|
3
|
-
Status: proposed
|
|
4
|
-
|
|
5
|
-
**Problem.** Durable records every task outcome, but only in its storage.
|
|
6
|
-
`service.log` shows startup, sends and a few delivery warnings. On 2026-10-25
|
|
7
|
-
the nightly memory reflection failed three times because the model child had
|
|
8
|
-
exhausted a provider quota; the process, the port and the root HTTP check
|
|
9
|
-
looked healthy and `service.log` had no line about it. The failure was only
|
|
10
|
-
visible by reading `durable/main.jsonl` or by a chat notice that was deduped.
|
|
11
|
-
|
|
12
|
-
**Example.**
|
|
13
|
-
|
|
14
|
-
```text
|
|
15
|
-
2026-10-25T00:14:48.924Z Model call failed {
|
|
16
|
-
kind: 'pi.generation', task: 183434, conversation: 183426, status: 'failed',
|
|
17
|
-
provider: 'openai-codex', model: 'gpt-6.1-sol',
|
|
18
|
-
reason: 'You have hit your ChatGPT usage limit (plus plan). Try again in ~291 min.'
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**Decision.**
|
|
23
|
-
|
|
24
|
-
- `service.log` gets a `console.error` line with an ISO timestamp for a failure
|
|
25
|
-
that breaks normal operation. A failure qualifies when either holds: a chat
|
|
26
|
-
or scheduled job does not get the result it was waiting for, or the system
|
|
27
|
-
enters a state that needs an operator. Transient errors that recover on
|
|
28
|
-
their own, such as a retried provider error or a delivery whose status is
|
|
29
|
-
later confirmed, do not qualify.
|
|
30
|
-
- One module, `src/agent/error-log.ts`, owns these lines. It observes Durable
|
|
31
|
-
commits synchronously and never calls Session APIs. Each error kind is one
|
|
32
|
-
branch with its own message name; adding a kind states which of the two
|
|
33
|
-
conditions it meets.
|
|
34
|
-
- The first kind is a model call that ends `failed` or `faulted`: the line
|
|
35
|
-
carries the task, conversation, provider, model and the reason truncated to
|
|
36
|
-
200 characters. It carries no prompt, tool argument or input content.
|
|
37
|
-
- Other task kinds, including scheduled jobs that fault, are not logged yet;
|
|
38
|
-
they are added under the same conditions when needed.
|
|
39
|
-
|
|
40
|
-
**Consequences.** A quota, auth or provider outage is visible in the service
|
|
41
|
-
log within the same commit that records it, so a health check that reads the
|
|
42
|
-
log catches it without a paid model probe. The web `/logs` page shows the same
|
|
43
|
-
lines. Durable records remain the full source of truth; the log is a pointer
|
|
44
|
-
to them, not a replacement.
|
package/ops/README.md
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
# Operations: installation, deployment and restart
|
|
2
|
-
|
|
3
|
-
The owner has authorized deployment after each completed change, including
|
|
4
|
-
verification, plus committing and pushing each completed intended project change
|
|
5
|
-
after the required checks. Do not ask again for routine deployment or commit/push
|
|
6
|
-
approval in that scope. Stage explicit intended paths; exclude unrelated drafts,
|
|
7
|
-
temporary files and credentials. Verify the pushed remote revision; stop on a
|
|
8
|
-
rejected push, never force push. This does not authorize paid probes, test messages,
|
|
9
|
-
data cleanup or unrelated service changes. Stop for a new material safety or
|
|
10
|
-
data-integrity risk.
|
|
11
|
-
Never run old/new pipelines concurrently or use production data in tests.
|
|
12
|
-
Implementation readiness and open decisions are tracked in GitHub #33.
|
|
13
|
-
|
|
14
|
-
For ordinary updates, use [Routine deployment and restart](#routine-deployment-and-restart).
|
|
15
|
-
The one-time legacy migration is archived at tag `archive/legacy-migration` (ADR 0043).
|
|
16
|
-
|
|
17
|
-
## Prerequisites
|
|
18
|
-
|
|
19
|
-
- macOS, Node >=22.22.2, Python 3 and authenticated Pi models.
|
|
20
|
-
- Terminal/Node Full Disk Access for Messages' SQLite database; Automation and
|
|
21
|
-
Accessibility permissions for Messages.app/rich-text sends; `/usr/bin/sips`.
|
|
22
|
-
- The existing memory CLI at `WORKING_DIR/skills/file-memory/memory_cli.py`.
|
|
23
|
-
- The installed `pi-browser` CLI and its pinned Playwright runtime. Run
|
|
24
|
-
`pi-browser setup` when needed and authorized; preflight never installs it. Preflight
|
|
25
|
-
resolves the wrapper in Pi's agent `bin/`, or a preflight-only
|
|
26
|
-
`PI_BROWSER_CLI_PATH` override, and runs `--version` with temporary browser state
|
|
27
|
-
that is removed afterward. A wrapper whose `doctor` reports MISSING is not ready.
|
|
28
|
-
Version readiness does not prove browser privacy, login or per-chat isolation.
|
|
29
|
-
- Reviewed native SQLite and image dependencies. Install dependencies with
|
|
30
|
-
`npm ci --ignore-scripts`; if native binaries are missing, review and approve
|
|
31
|
-
their build/download separately rather than silently enabling install scripts.
|
|
32
|
-
|
|
33
|
-
Safe local checks (no live models/messages/workspace):
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
npm run check
|
|
37
|
-
npm test
|
|
38
|
-
node --experimental-strip-types ops/preflight.mjs
|
|
39
|
-
node --experimental-strip-types src/cli.ts --help
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
For changes to the built-in browser, also run
|
|
43
|
-
`node --experimental-strip-types ops/browser-smoke.mjs`. This uses real headless
|
|
44
|
-
Chrome and the installed pinned CLI with temporary HOME/profiles, loopback pages
|
|
45
|
-
and synthetic login state; it never opens a personal profile or sends a message.
|
|
46
|
-
See [browser isolation and retention](../docs/browser.md).
|
|
47
|
-
|
|
48
|
-
`WORKING_DIR`, `WEB_HOST`, `WEB_PORT`, `WEB_ENABLED` and `MESSAGES_DB_PATH`
|
|
49
|
-
are explicit host configuration. `npm start`, the
|
|
50
|
-
executable CLI and the generated launchd job enable Node's environment proxy
|
|
51
|
-
support. `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` (and their lowercase forms) and
|
|
52
|
-
`NODE_OPTIONS` are retained when generating the job; review them before install.
|
|
53
|
-
CLI execution loads the current directory's `.env` using Node's parser; existing
|
|
54
|
-
environment values take precedence and a missing file is allowed. Set
|
|
55
|
-
`DOTENV_CONFIG_PATH` for another file. Module imports and `--help` do not load it.
|
|
56
|
-
Other read errors stop startup without printing file contents. `.env` is trusted
|
|
57
|
-
operator configuration, not a message attachment. Keep it private and out of Git.
|
|
58
|
-
Installation pins its current directory as the job's working directory, so the
|
|
59
|
-
same configuration path is used at launch. Selected environment values in the
|
|
60
|
-
plist are a snapshot; regenerate a reviewed job to change those overrides.
|
|
61
|
-
The job also pins the selected Pi agent directory as an absolute
|
|
62
|
-
`PI_CODING_AGENT_DIR` (including the default). Relative paths and `~` are resolved
|
|
63
|
-
at installation, after `.env` loading, so daemon HOME/cwd changes do not select a
|
|
64
|
-
different auth.json, models.json, settings.json or skill root. This selects a
|
|
65
|
-
configuration location; it does not copy credentials or grant access to it.
|
|
66
|
-
|
|
67
|
-
No proxy credentials are printed by installation. Application startup configures
|
|
68
|
-
one process-owned Undici transport after `.env` loading and before model/auth
|
|
69
|
-
refresh. Global Pi `httpProxy` supplies HTTP/HTTPS defaults; explicit environment
|
|
70
|
-
values win, lowercase proxy/bypass values take precedence, and `NO_PROXY` is
|
|
71
|
-
honored. Project-local `httpProxy` is ignored. `/reload` applies changed settings
|
|
72
|
-
for subsequent requests while old clients drain active requests. Shutdown stops
|
|
73
|
-
messaging and Harness work before releasing owned HTTP resources. Standalone
|
|
74
|
-
callers with injected runtimes retain transport control; deliberate fetch
|
|
75
|
-
overrides and dispatchers owned by other callers are not replaced on cleanup.
|
|
76
|
-
|
|
77
|
-
Pi settings in the agent directory and `WORKING_DIR/.pi/settings.json` supply
|
|
78
|
-
request policy; `/reload` refreshes it. `retry.provider.timeoutMs` overrides the
|
|
79
|
-
provider request timeout, otherwise `httpIdleTimeoutMs` supplies that default
|
|
80
|
-
(300000 ms; 0 uses Pi's effectively unlimited value). This forwards a request
|
|
81
|
-
option to supporting providers. Separately, application HTTP transport uses
|
|
82
|
-
`httpIdleTimeoutMs` for native header/body inactivity timers; 0 disables those
|
|
83
|
-
timers. An explicit provider request timeout can still limit a request when
|
|
84
|
-
socket-idle timers are disabled. Provider retry limits/delay caps and `retry`
|
|
85
|
-
generation policy are executed by Pi/Durable; transport remains SSE. These do
|
|
86
|
-
not replace the old host activity timer, prompt maximum-duration or
|
|
87
|
-
compaction-deadline behavior.
|
|
88
|
-
|
|
89
|
-
Web defaults to localhost:7750 and has no authentication. Never expose it to
|
|
90
|
-
untrusted networks.
|
|
91
|
-
|
|
92
|
-
Lifecycle logs use ISO timestamps on stdout/stderr: application startup (PID,
|
|
93
|
-
Node version and workspace), agent readiness/reload, the actual Web bind address
|
|
94
|
-
or disabled state, messaging readiness (cursor and poll/compaction intervals),
|
|
95
|
-
shutdown signal, resource cleanup and failures. Readiness is logged only after
|
|
96
|
-
the corresponding initialization succeeds; it is not a model or delivery health
|
|
97
|
-
check. Idle polls do not produce log lines. These lifecycle entries do not include
|
|
98
|
-
message text, tool arguments or proxy/auth configuration.
|
|
99
|
-
The generated launchd job redirects both streams to `WORKING_DIR/service.log`.
|
|
100
|
-
Foreground runs use the terminal or the controller's selected redirection; the
|
|
101
|
-
application does not open a second log file.
|
|
102
|
-
|
|
103
|
-
## npm publishing
|
|
104
|
-
|
|
105
|
-
The public package contains compiled JavaScript and Web assets, not a TypeScript
|
|
106
|
-
executable under `node_modules`. `npm pack` builds through `prepack`;
|
|
107
|
-
`npm run test:package` verifies the packed executable and inert installer in a
|
|
108
|
-
temporary HOME/workspace without models or Messages.
|
|
109
|
-
|
|
110
|
-
`.github/workflows/publish.yml` uses the existing npm trusted publisher with
|
|
111
|
-
GitHub OIDC, Node 24 and `id-token: write`; no local npm login or token is needed.
|
|
112
|
-
It runs the full checks, tests and package smoke before publishing with provenance.
|
|
113
|
-
Future releases push a matching `v<package-version>` tag. To publish an existing
|
|
114
|
-
version after fixing packaging, dispatch the workflow on main with that exact
|
|
115
|
-
version; never move an already published GitHub tag.
|
|
116
|
-
|
|
117
|
-
```sh
|
|
118
|
-
gh workflow run publish.yml --ref main -f version=1.0.0
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
An installed npm service runs `dist/cli.js`; source deployments continue to use
|
|
122
|
-
`src/cli.ts`. Package installation does not start or reload a service.
|
|
123
|
-
|
|
124
|
-
## Service identity
|
|
125
|
-
|
|
126
|
-
New `install` output uses `org.pi-imessage.service` and
|
|
127
|
-
`org.pi-imessage.service.plist`. For an installed
|
|
128
|
-
`me.changchen.pi-imessage-durable` job, privately preserve its plist/environment,
|
|
129
|
-
gracefully stop and unload that exact controller, verify PID/lock/port release,
|
|
130
|
-
then write and load the reviewed neutral job. Do not leave the old KeepAlive
|
|
131
|
-
controller loaded or start a Terminal copy alongside launchd. A foreground
|
|
132
|
-
Terminal service with no old-label job needs no controller change. Never rename
|
|
133
|
-
unrelated legacy jobs as part of this migration.
|
|
134
|
-
|
|
135
|
-
## Routine deployment and restart
|
|
136
|
-
|
|
137
|
-
### Choose the smallest operation
|
|
138
|
-
|
|
139
|
-
| Change | Action |
|
|
140
|
-
|---|---|
|
|
141
|
-
| Server, agent, transport or imported TypeScript | Restart from a new release directory; verify the change. |
|
|
142
|
-
| Web `app.js` / `style.css` only | Verify the served assets; no restart (the file cache checks metadata). |
|
|
143
|
-
| Documentation only | Commit and push; no reload or restart. |
|
|
144
|
-
| Configuration supported by `/reload` | Reload; restart only for startup-only configuration. Never send an iMessage to trigger it. |
|
|
145
|
-
|
|
146
|
-
Deploying means the change is live and verified, then committed and pushed.
|
|
147
|
-
|
|
148
|
-
### Release directories
|
|
149
|
-
|
|
150
|
-
The service runs a fixed commit, never the main checkout:
|
|
151
|
-
|
|
152
|
-
```sh
|
|
153
|
-
SHA=$(git -C /Users/clawbot/pi-imessage-next rev-parse origin/main)
|
|
154
|
-
RELEASE=/Users/clawbot/pi-imessage-releases/$SHA
|
|
155
|
-
git -C /Users/clawbot/pi-imessage-next worktree add --detach "$RELEASE" "$SHA"
|
|
156
|
-
(cd "$RELEASE" && npm ci --ignore-scripts && node --experimental-strip-types ops/preflight.mjs)
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Never edit a release directory; rollback is a routine restart from the previous
|
|
160
|
-
one. Keep the latest three; remove older ones with `git worktree remove` (no
|
|
161
|
-
`--force`) when no process or plist uses them. A release directory has no `.env`:
|
|
162
|
-
set `DOTENV_CONFIG_PATH` to the main checkout's `.env`.
|
|
163
|
-
|
|
164
|
-
### 1. Identify the running service
|
|
165
|
-
|
|
166
|
-
```sh
|
|
167
|
-
launchctl list | grep -Ei 'pi-imessage|pi-web' || true
|
|
168
|
-
lsof -nP -iTCP:7750 -sTCP:LISTEN # take PID from here, never from notes
|
|
169
|
-
ps -p "$PID" -o pid,ppid,lstart,command
|
|
170
|
-
lsof -a -p "$PID" -d cwd,1,2 -Fn
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
The claw baseline is a **Terminal foreground process** (not launchd) running from
|
|
174
|
-
a release directory, with `WORKING_DIR=~/.pi/imessage-next`,
|
|
175
|
-
`PI_CODING_AGENT_DIR=~/.pi/agent`, Node 22, logs in `WORKING_DIR/service.log`, and
|
|
176
|
-
the owner-selected unauthenticated bind `0.0.0.0:7750` (trusted networks only).
|
|
177
|
-
A similarly named Pi web job does not control this service. Do not switch
|
|
178
|
-
controllers, install launchd or upgrade Node during a routine restart.
|
|
179
|
-
|
|
180
|
-
### 2. Record the baseline (ADR 0038)
|
|
181
|
-
|
|
182
|
-
Restart at any time; no running work defers it. Before stopping, record the
|
|
183
|
-
counts of `sending` / `unknown` receipts from `/chat/data` (counts only, no chat
|
|
184
|
-
text). Never reset cursors, delete state or replay uncertain sends. Do not use
|
|
185
|
-
`/health/model`: it is a paid request.
|
|
186
|
-
|
|
187
|
-
### 3. Run one private controller
|
|
188
|
-
|
|
189
|
-
From a chat, the assistant calls `restart_service` with the full commit hash of
|
|
190
|
-
the prepared release directory (ADR 0052). The host writes the controller
|
|
191
|
-
directory below itself and opens the controller only after that tool call has
|
|
192
|
-
settled; the call returns the directory and the next call verifies (step 4).
|
|
193
|
-
The manual procedure below is for operators and for a cold start when no
|
|
194
|
-
service is running.
|
|
195
|
-
|
|
196
|
-
In a `mktemp -d /tmp/pi-imessage-restart.XXXXXX` directory with `umask 077`:
|
|
197
|
-
|
|
198
|
-
- `environment.sh`: shell-quoted exports of the running process's `WORKING_DIR`,
|
|
199
|
-
`PI_CODING_AGENT_DIR`, `DOTENV_CONFIG_PATH`, Web/DB overrides, proxy variables
|
|
200
|
-
(both cases), `NODE_OPTIONS`, search keys, HOME, locale, `TZ` and PATH (keep
|
|
201
|
-
`~/.pi/agent/bin` and the Node directory). Write it directly; never print
|
|
202
|
-
values, `ps eww` output or credentials.
|
|
203
|
-
- `control.sh`: shell-quoted `OLD_PID`, `OLD_STARTED` (exact `ps -o lstart=`),
|
|
204
|
-
`EXPECTED_COMMAND`, `REPO` (the release directory), `NODE`, `WORKING_DIR`,
|
|
205
|
-
`WEB_PORT` and `LOG_FILE`.
|
|
206
|
-
- `restart.command`: a copy of [`ops/restart.command`](restart.command) from the
|
|
207
|
-
release directory. It checks the old PID's start time and command, sends
|
|
208
|
-
SIGTERM, waits up to 30 s, refuses to start while the lock or port is held,
|
|
209
|
-
writes `controller.log` and `new.pid`, deletes `environment.sh` and then
|
|
210
|
-
`exec`s the new service process.
|
|
211
|
-
|
|
212
|
-
Launch it once with `chmod 700` and `open -a Terminal "$DEPLOY_DIR/restart.command"`
|
|
213
|
-
(`osascript` `do script` times out on claw). The separate Terminal keeps the
|
|
214
|
-
controller alive when the initiating agent's own turn is interrupted. For a
|
|
215
|
-
launchd controller, use that job's lifecycle instead; never `launchctl kickstart -k`
|
|
216
|
-
from inside the service.
|
|
217
|
-
|
|
218
|
-
A chat that writes its own controller through `bash` instead of
|
|
219
|
-
`restart_service` must end that call right after `open`: no `sleep`, polling or
|
|
220
|
-
verification in the same call. Tools are replay-safe by default (ADR 0041), so a
|
|
221
|
-
call still running when the controller stops the service reruns on every start,
|
|
222
|
-
launches another controller and loops (#39). Verify in a later call.
|
|
223
|
-
|
|
224
|
-
### 4. Verify
|
|
225
|
-
|
|
226
|
-
In a new tool call, read `controller.log`, `new.pid`, the service log and the
|
|
227
|
-
listener first. Never launch the controller twice before the first outcome is
|
|
228
|
-
known. Require:
|
|
229
|
-
|
|
230
|
-
1. Old PID gone; exactly one new process and listener, cwd in the release
|
|
231
|
-
directory.
|
|
232
|
-
2. Fresh `Application starting`, `Agent ready`, `Web server listening`,
|
|
233
|
-
`Messaging ready` and `Application started`; no startup error.
|
|
234
|
-
3. Root and changed endpoints return 200; check UI changes in a browser.
|
|
235
|
-
4. Interrupted work continues; scheduler deadlines are unchanged; receipt
|
|
236
|
-
counts change only by new sends and sends interrupted by the stop. After
|
|
237
|
-
`restart_service`, the old log shows `Restart requested` with the
|
|
238
|
-
controller directory.
|
|
239
|
-
|
|
240
|
-
On a timeout, lock, occupied port or startup failure, stop and report it. Do not
|
|
241
|
-
force-kill, steal the lock, reinstall dependencies or restore a workspace snapshot.
|
|
242
|
-
|
|
243
|
-
Routine deployments do not run the live smoke. Only when the owner authorizes a
|
|
244
|
-
paid model request and a real message, run
|
|
245
|
-
`node --experimental-strip-types ops/smoke.ts --live --chat CHAT_GUID`: it checks
|
|
246
|
-
`/health/model`, waits for the operator's message from the device and reports
|
|
247
|
-
the new reply's receipt. Never resend a reply that is not `sent`.
|
|
248
|
-
Record the deployment (time, commit, PID, checks, unverified limits) with
|
|
249
|
-
`pi-memory`, delete the `/tmp` controller directory, and report **deployed and
|
|
250
|
-
verified** or the blocker. Release gates and evidence live in GitHub #33.
|
package/ops/preflight.mjs
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { execFile } from "node:child_process";
|
|
3
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import Database from "better-sqlite3";
|
|
9
|
-
import sharp from "sharp";
|
|
10
|
-
|
|
11
|
-
assert.equal(process.platform, "darwin", "Messages transport requires macOS");
|
|
12
|
-
const version = process.versions.node.split(".").map(Number);
|
|
13
|
-
assert.ok(version[0] > 22 || (version[0] === 22 && (version[1] > 22 || (version[1] === 22 && version[2] >= 2))), "Node 22.22.2 or newer is required");
|
|
14
|
-
const database = new Database(":memory:");
|
|
15
|
-
try { assert.equal(database.prepare("SELECT 1 AS value").get().value, 1); } finally { database.close(); }
|
|
16
|
-
assert.equal((await sharp({ create: { width: 1, height: 1, channels: 3, background: "white" } }).jpeg().toBuffer()).length > 0, true);
|
|
17
|
-
const browserState = await mkdtemp(join(tmpdir(), "imessage-browser-preflight-"));
|
|
18
|
-
try {
|
|
19
|
-
// doctor can report MISSING with exit 0; --version must reach the installed runtime.
|
|
20
|
-
await promisify(execFile)(process.env.PI_BROWSER_CLI_PATH ?? join(getAgentDir(), "bin", "pi-browser"), ["--version"], {
|
|
21
|
-
env: { ...process.env, PI_BROWSER_HOME: browserState }, timeout: 10000, maxBuffer: 65536,
|
|
22
|
-
});
|
|
23
|
-
} catch (error) {
|
|
24
|
-
throw new Error("Browser CLI readiness failed; install the pinned runtime with pi-browser setup", { cause: error });
|
|
25
|
-
} finally {
|
|
26
|
-
await rm(browserState, { recursive: true, force: true });
|
|
27
|
-
}
|
|
28
|
-
console.log("Preflight passed: macOS, Node, native SQLite/image dependencies and browser CLI runtime. No model, Messages or production workspace was accessed.");
|