@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
|
@@ -6,9 +6,9 @@ Business code belongs in `WORKING_DIR/extensions/<name>/index.ts` with its own
|
|
|
6
6
|
host code, not a sandbox or an SDK `ExtensionAPI` adapter. Back up the extension
|
|
7
7
|
directories with their configuration. The
|
|
8
8
|
[system-context](../examples/workspace-extensions/system-context/) example loads
|
|
9
|
-
an optional workspace summary into the prompt
|
|
9
|
+
an optional workspace summary into the prompt.
|
|
10
10
|
|
|
11
|
-
Tools without `replay` rerun after an interruption
|
|
11
|
+
Tools without `replay` rerun after an interruption. Declare
|
|
12
12
|
`replay: "unsafe"` for an effect that must not repeat.
|
|
13
13
|
|
|
14
14
|
## Factory and reload
|
|
@@ -49,8 +49,12 @@ module.exports = ({ config, defineExtension, defineTask }) => {
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
The factory receives `workingDir`, parsed `config`, `Type`, `defineExtension`,
|
|
52
|
-
`defineTool`, `defineDoc`, `defineTask`, `configure`, `section`,
|
|
53
|
-
`
|
|
52
|
+
`defineTool`, `defineDoc`, `defineTask`, `configure`, `section`, `hook` with
|
|
53
|
+
`GenerationTask` (generation hooks such as a per-request `beforeRequest` message
|
|
54
|
+
rewrite), the generic `ScheduledOutbox` document token, and `durable`, the whole
|
|
55
|
+
Durable SDK, for anything else a plain Durable extension uses (for example
|
|
56
|
+
`durable.LiveDoc` or `durable.ResetEntry`). All primitives are the host's pinned
|
|
57
|
+
versions.
|
|
54
58
|
Return a native extension with optional `schedules`; tools and multiple tasks
|
|
55
59
|
work without any schedules. Startup and `/reload` scan direct, non-hidden child
|
|
56
60
|
directories only, never skills or attachments. `reload_extensions` lets the chat
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kingcrab/pi-imessage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A minimal, self-managing iMessage bot powered by Pi Durable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -10,9 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"docs",
|
|
13
|
-
"examples"
|
|
14
|
-
"ops/README.md",
|
|
15
|
-
"ops/preflight.mjs"
|
|
13
|
+
"examples"
|
|
16
14
|
],
|
|
17
15
|
"publishConfig": {
|
|
18
16
|
"access": "public",
|
|
@@ -28,7 +26,7 @@
|
|
|
28
26
|
"scripts": {
|
|
29
27
|
"build": "tsc -p tsconfig.build.json && cp src/web/app.js src/web/style.css dist/web/",
|
|
30
28
|
"prepack": "npm run build",
|
|
31
|
-
"test:package": "npm run build && node
|
|
29
|
+
"test:package": "npm run build && node scripts/package-smoke.mjs",
|
|
32
30
|
"start": "node --use-env-proxy --experimental-strip-types src/cli.ts serve",
|
|
33
31
|
"check": "tsc --noEmit && biome check .",
|
|
34
32
|
"test": "vitest run"
|
|
@@ -36,6 +34,7 @@
|
|
|
36
34
|
"dependencies": {
|
|
37
35
|
"@earendil-works/chord": "1.4.0",
|
|
38
36
|
"@earendil-works/pi-ai": "1.4.0",
|
|
37
|
+
"@earendil-works/pi-codemode": "1.4.0",
|
|
39
38
|
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
40
39
|
"@earendil-works/pi-durable": "1.4.0",
|
|
41
40
|
"better-sqlite3": "13.0.3",
|
package/dist/agent/compaction.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
2
|
-
import { CompactionEntry, LiveDoc, ResetEntry } from "@earendil-works/pi-durable";
|
|
3
|
-
import { Chats } from "./chats.js";
|
|
4
|
-
// Native compact() uses reason "manual" for both a command and a maintenance tick.
|
|
5
|
-
// Record exact timer task IDs, not conversation IDs, so concurrent manual work stays visible.
|
|
6
|
-
const scheduled = new WeakMap();
|
|
7
|
-
/** Resolve admission before classifying: the commit may be observed before compact() returns its ID. */
|
|
8
|
-
export async function takeScheduledCompaction(harness, taskId) {
|
|
9
|
-
const state = scheduled.get(harness);
|
|
10
|
-
if (!state)
|
|
11
|
-
return false;
|
|
12
|
-
await Promise.allSettled([...state.admissions]);
|
|
13
|
-
return state.tasks.delete(taskId);
|
|
14
|
-
}
|
|
15
|
-
/** Admit native compactions, not another summarization loop; Durable decides whether a cut is needed. */
|
|
16
|
-
export async function compactChats(harness, admissionFailed) {
|
|
17
|
-
const tasks = [];
|
|
18
|
-
const chats = await harness.snapshot(Chats, BACKGROUND_CONTEXT);
|
|
19
|
-
for (const chat of chats?.items ?? []) {
|
|
20
|
-
try {
|
|
21
|
-
if ((await harness.snapshot(LiveDoc, chat.conversationId, BACKGROUND_CONTEXT))?.compactions?.length)
|
|
22
|
-
continue;
|
|
23
|
-
const conversation = await harness.conversation(chat.conversationId, BACKGROUND_CONTEXT);
|
|
24
|
-
const newest = (await conversation?.entries({}, 1, undefined, BACKGROUND_CONTEXT))?.items[0];
|
|
25
|
-
if (!conversation || !newest || CompactionEntry.is(newest) || ResetEntry.is(newest))
|
|
26
|
-
continue;
|
|
27
|
-
let state = scheduled.get(harness);
|
|
28
|
-
if (!state) {
|
|
29
|
-
state = { admissions: new Set(), tasks: new Set() };
|
|
30
|
-
scheduled.set(harness, state);
|
|
31
|
-
}
|
|
32
|
-
const quiet = state;
|
|
33
|
-
const admission = conversation.compact(undefined, BACKGROUND_CONTEXT).then((taskId) => {
|
|
34
|
-
quiet.tasks.add(taskId);
|
|
35
|
-
return taskId;
|
|
36
|
-
});
|
|
37
|
-
quiet.admissions.add(admission);
|
|
38
|
-
try {
|
|
39
|
-
tasks.push(await admission);
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
quiet.admissions.delete(admission);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
admissionFailed?.();
|
|
47
|
-
console.warn("Scheduled compaction admission failed", chat.chatGuid, error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return tasks;
|
|
51
|
-
}
|
package/dist/agent/error-log.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const REASON_LIMIT = 200;
|
|
2
|
-
const failedAssistant = (message) => message.role === "assistant" && message.stopReason === "error";
|
|
3
|
-
/**
|
|
4
|
-
* Service-log error lines for failures that break normal operation (ADR 0053). Durable keeps a failed
|
|
5
|
-
* task only in its records, so without this an exhausted provider quota leaves stdout silent while the
|
|
6
|
-
* process, port and HTTP checks look healthy. Each error kind is one branch here; the first is a model
|
|
7
|
-
* call that ends failed, which leaves the chat or scheduled job without the answer it was waiting for.
|
|
8
|
-
*/
|
|
9
|
-
export function logErrors(harness) {
|
|
10
|
-
return harness.subscribeCommits(({ changes }) => {
|
|
11
|
-
for (const change of changes) {
|
|
12
|
-
if (change.type !== "task" || change.value.kind !== "pi.generation")
|
|
13
|
-
continue;
|
|
14
|
-
const task = change.value;
|
|
15
|
-
if (task.state.status !== "terminal")
|
|
16
|
-
continue;
|
|
17
|
-
const { outcome } = task.state;
|
|
18
|
-
if (outcome.status === "completed" || outcome.status === "aborted")
|
|
19
|
-
continue;
|
|
20
|
-
// The failing assistant message is committed together with the task and names provider and model.
|
|
21
|
-
const failed = changes
|
|
22
|
-
.flatMap((item) => item.type === "entry" && item.value.conversationId === task.conversationId
|
|
23
|
-
? (item.value.model ?? [])
|
|
24
|
-
: [])
|
|
25
|
-
.find(failedAssistant);
|
|
26
|
-
console.error(new Date().toISOString(), "Model call failed", {
|
|
27
|
-
kind: task.kind,
|
|
28
|
-
task: task.id,
|
|
29
|
-
conversation: task.conversationId,
|
|
30
|
-
status: outcome.status,
|
|
31
|
-
provider: failed?.provider,
|
|
32
|
-
model: failed?.model,
|
|
33
|
-
reason: (outcome.error?.message ?? outcome.reason ?? "").slice(0, REASON_LIMIT),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { access, chmod, copyFile, mkdtemp, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
|
-
import { Type } from "@earendil-works/pi-ai";
|
|
6
|
-
import { defineExtension, defineTool } from "@earendil-works/pi-durable";
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
|
-
/** Variables that describe the old shell session or that the controller sets itself. */
|
|
9
|
-
const EXCLUDED_ENVIRONMENT = new Set(["_", "PWD", "OLDPWD", "SHLVL", "TERM_SESSION_ID"]);
|
|
10
|
-
function shellQuote(value) {
|
|
11
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
12
|
-
}
|
|
13
|
-
async function processField(field) {
|
|
14
|
-
const { stdout } = await execFileAsync("ps", ["-p", String(process.pid), "-o", `${field}=`]);
|
|
15
|
-
// Exactly what the controller's `$(ps ...)` yields: trailing newlines stripped, nothing else.
|
|
16
|
-
return stdout.replace(/\n+$/, "");
|
|
17
|
-
}
|
|
18
|
-
/** Writes the private controller directory of ops/README.md step 3 for the target release; starts nothing. */
|
|
19
|
-
export async function prepareRestart(options, release) {
|
|
20
|
-
if (!/^[0-9a-f]{40}$/.test(release))
|
|
21
|
-
throw new Error("release must be the full commit hash of a prepared release directory");
|
|
22
|
-
const target = join(dirname(options.currentRelease), release);
|
|
23
|
-
for (const entry of ["src/cli.ts", "node_modules", "ops/restart.command"]) {
|
|
24
|
-
await access(join(target, entry)).catch(() => {
|
|
25
|
-
throw new Error(`Release directory is not prepared: ${join(target, entry)} is missing`);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
const [started, command] = await Promise.all([processField("lstart"), processField("command")]);
|
|
29
|
-
const deployDir = await mkdtemp(join(options.deployRoot, "pi-imessage-restart."));
|
|
30
|
-
// The whole process environment, including .env values loaded at startup; the controller deletes the file.
|
|
31
|
-
const environment = Object.entries(process.env)
|
|
32
|
-
.filter(([name, value]) => value !== undefined && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !EXCLUDED_ENVIRONMENT.has(name))
|
|
33
|
-
.map(([name, value]) => `export ${name}=${shellQuote(value ?? "")}`);
|
|
34
|
-
await writeFile(join(deployDir, "environment.sh"), `${environment.join("\n")}\n`, { mode: 0o600 });
|
|
35
|
-
const control = {
|
|
36
|
-
OLD_PID: String(process.pid),
|
|
37
|
-
OLD_STARTED: started,
|
|
38
|
-
EXPECTED_COMMAND: command,
|
|
39
|
-
REPO: target,
|
|
40
|
-
NODE: process.execPath,
|
|
41
|
-
WORKING_DIR: options.workingDir,
|
|
42
|
-
WEB_PORT: process.env.WEB_PORT ?? "7750",
|
|
43
|
-
LOG_FILE: join(options.workingDir, "service.log"),
|
|
44
|
-
};
|
|
45
|
-
await writeFile(join(deployDir, "control.sh"), `${Object.entries(control)
|
|
46
|
-
.map(([name, value]) => `${name}=${shellQuote(value)}`)
|
|
47
|
-
.join("\n")}\n`, { mode: 0o600 });
|
|
48
|
-
const controller = join(deployDir, "restart.command");
|
|
49
|
-
await copyFile(join(target, "ops", "restart.command"), controller);
|
|
50
|
-
await chmod(controller, 0o700);
|
|
51
|
-
return { target, deployDir, controller };
|
|
52
|
-
}
|
|
53
|
-
/** The claw baseline: a Terminal window keeps the controller alive independently of this process. */
|
|
54
|
-
export async function launchInTerminal(controller) {
|
|
55
|
-
await execFileAsync("open", ["-a", "Terminal", controller], { timeout: 15_000 });
|
|
56
|
-
}
|
|
57
|
-
/** The chat assistant restarts the service through the host instead of a bash command that stops its own host. */
|
|
58
|
-
export function restartExtension(options) {
|
|
59
|
-
let scheduled;
|
|
60
|
-
return defineExtension({
|
|
61
|
-
name: "restart",
|
|
62
|
-
tools: [
|
|
63
|
-
defineTool({
|
|
64
|
-
name: "restart_service",
|
|
65
|
-
description: "Restart this service into a prepared release directory: a sibling of the running one named by its full commit hash, with npm ci and ops/preflight.mjs done. The service stops only after this call has settled; end the call at once and verify in a later call: controller.log and new.pid in the returned directory, then the service log, the listener and the running release directory. Never call it again before the first outcome is known.",
|
|
66
|
-
parameters: Type.Object({
|
|
67
|
-
release: Type.String({ description: "Full commit hash of the prepared release directory" }),
|
|
68
|
-
}),
|
|
69
|
-
// A rerun would launch a second controller (#39); an interrupted call is reported to the model instead.
|
|
70
|
-
replay: "unsafe",
|
|
71
|
-
async execute(args, api) {
|
|
72
|
-
if (scheduled)
|
|
73
|
-
throw new Error(`A restart is already scheduled from ${scheduled}`);
|
|
74
|
-
const prepared = await prepareRestart(options, args.release);
|
|
75
|
-
scheduled = prepared.deployDir;
|
|
76
|
-
console.log(new Date().toISOString(), "Restart requested", {
|
|
77
|
-
release: args.release,
|
|
78
|
-
controller: prepared.deployDir,
|
|
79
|
-
task: api.taskId,
|
|
80
|
-
});
|
|
81
|
-
void options
|
|
82
|
-
.settled(api.taskId)
|
|
83
|
-
.then(() => options.launch(prepared.controller))
|
|
84
|
-
.catch((error) => {
|
|
85
|
-
scheduled = undefined;
|
|
86
|
-
console.error(new Date().toISOString(), "Restart launch failed", error);
|
|
87
|
-
});
|
|
88
|
-
return {
|
|
89
|
-
output: [
|
|
90
|
-
{
|
|
91
|
-
type: "text",
|
|
92
|
-
text: `Restart into ${prepared.target} is scheduled; the service stops after this call settles. Controller directory: ${prepared.deployDir}. Verify in a later call.`,
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
],
|
|
99
|
-
});
|
|
100
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 0003. Replies are sent at most once
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-04; owner accepted 2026-10-05)
|
|
4
|
-
|
|
5
|
-
**Context.** Sending through Messages.app and recording it in Durable cannot be one atomic step. A crash in between either loses or repeats a reply.
|
|
6
|
-
|
|
7
|
-
**Decision.** For each final answer: record `sending` in the conversation, send, then record `sent`. On restart, `sending` becomes `unknown`: it is logged and shown in the web UI, and never resent automatically; to resend, use `/send`. This covers agent replies; `/send`, cron and reminders report their result to the caller or run log.
|
|
8
|
-
|
|
9
|
-
**Consequences.** A crash in that window of a few milliseconds can drop one reply. Nothing is ever sent twice.
|
|
10
|
-
|
|
11
|
-
**Rejected.** Send, then record (can send twice). Confirming delivery through `chat.db` GUIDs (complex and unreliable).
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 0005. Import old history once; start with an empty context
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-04; owner accepted 2026-10-05)
|
|
4
|
-
|
|
5
|
-
**Context.** Old data per chat: `WORKING_DIR/<chat>/log.jsonl` (about 28,000 lines across 12 chats on claw), plus SDK sessions, goals, subagents and several GB of images.
|
|
6
|
-
|
|
7
|
-
**Decision.** `migrate/migrate.ts` runs once at cutover. Each `log.jsonl` becomes user and assistant entries in that chat's conversation, followed by one reset, so the model starts with an empty context. Attachments move to `WORKING_DIR/attachments/<chat>/`, and entries keep the path. The agent searches old messages by grepping `WORKING_DIR/durable/`; the system prompt says where. SDK sessions, goals, subagents and scratch are archived once, then deleted.
|
|
8
|
-
|
|
9
|
-
**Consequences.** The model doesn't remember pre-cutover talk until it searches. The web UI shows old and new history the same way.
|
|
10
|
-
|
|
11
|
-
**Rejected.** Importing the SDK context (different format). A summary per chat at reset (one model call per chat). A separate archive folder.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 0008. Compact every 6 hours; `/run` replaces `/goal`
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-04; owner accepted 2026-10-05)
|
|
4
|
-
|
|
5
|
-
**Context.** The old bot reset every chat nightly, and used `pi-goal-x` for long tasks, which brought task trees, contracts and reviewers we don't need.
|
|
6
|
-
|
|
7
|
-
**Decision.** Every 6 hours, each chat with new entries since its last compaction is compacted in the background; no chat notifications. Durable's automatic compaction near the context limit stays on. `/run <duration> [task]` keeps the run going through an `onYield` hook until the task is done, the user is needed, or the deadline passes. One run per conversation; `/run-stop`, `/stop` and `/new` end it; it does not survive a restart.
|
|
8
|
-
|
|
9
|
-
**Consequences.** Context is never wiped on a schedule. `/run` spends tokens until it stops.
|
|
10
|
-
|
|
11
|
-
**Rejected.** Nightly reset. Migrating `pi-goal-x`.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 0009. Switch over in one step
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-04; owner accepted 2026-10-05)
|
|
4
|
-
|
|
5
|
-
**Context.** Running old and new pipelines side by side (a per-chat canary) needs guards against processing a message twice. That was much of #31's complexity.
|
|
6
|
-
|
|
7
|
-
**Decision.** Build in a new repository, `pi-imessage-next`. At cutover: stop the old service, run the migration (which also saves the current `chat.db` row as the watch cursor), start the new service, then push the new code to `daya0576/pi-imessage` as one commit. Rollback: stop the new service, restore the `WORKING_DIR` backup, start the old release.
|
|
8
|
-
|
|
9
|
-
**Consequences.** Minutes of downtime; messages that arrive meanwhile are picked up from the cursor, and losing a few is acceptable. No code for running two pipelines.
|
|
10
|
-
|
|
11
|
-
**Rejected.** Per-chat canary.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# 0013. Use Durable directly; keep adapters thin
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-04; owner accepted 2026-10-05)
|
|
4
|
-
|
|
5
|
-
**Context.** The drafts grew host state that Durable already keeps (per-command receipts with running/unknown recovery, a reply scan over every settled submission) and copies of other systems (a gzip/base64 Python memory backend inside TypeScript, a vendored 1,000-line scheduler). [0001](0001-durable-owns-state.md) says Durable owns chat state; this records where our code stops.
|
|
6
|
-
|
|
7
|
-
**Decision.**
|
|
8
|
-
|
|
9
|
-
- **Commands** call Durable APIs directly (`configure`, `compact`, `reset`, task abort). Each command is first written to the chat conversation as a passive entry with the message GUID as request ID, so a repeated row is skipped by Durable's dedupe. An interrupted command is not replayed. No command receipt state.
|
|
10
|
-
- **`/reload`** rereads settings, models and auth, AGENTS and skills, and reinstalls our extensions under the same names; Durable's registry replaces them in place. Running calls finish on their old code. Nothing is paused or cancelled.
|
|
11
|
-
- **Memory tools** run the store's own CLI, `WORKING_DIR/skills/file-memory/memory_cli.py`, which finds its data next to itself. No embedded or rewritten backend; a missing CLI is a tool error.
|
|
12
|
-
- **Scheduler** keeps the clock: cron expressions, timezones, reminders and their files (`cron/jobs.json`, reminders). When a job fires it submits to Durable with a stable request ID, or sends directly with a receipt. After acceptance, Durable owns execution and recovery; the scheduler never retries model work. Durable 1.0.2 has task `sleep` but no cron or reminder service, so it is not used as one.
|
|
13
|
-
- **Web pages** are read-only: they show Harness state and watches (history, live state, task graph, usage) and have no buttons. They keep no execution log of their own. The endpoints in [the HTTP API](../api.md) stay; endpoints used only by the old page buttons (per-chat reply toggle, PT task actions) are removed. Replies are enabled through the settings allowlist; cron jobs are run or paused through the API.
|
|
14
|
-
- **Our documents** hold only what Durable cannot know: the `chat.db` cursor, the chat-to-conversation map, send receipts and the active run with its deadline.
|
|
15
|
-
|
|
16
|
-
**Consequences.** Little host recovery code beyond send receipts. Memory needs Python and the store's CLI at runtime (Python is already a prerequisite). The scheduler stays a larger, separate module, isolated behind one submit or send call.
|
|
17
|
-
|
|
18
|
-
**Rejected.** Command receipts and a command state machine. Pausing work around `/reload`. Embedding or reimplementing the memory backend. Rewriting cron and reminders as Durable tasks. A separate execution log for the UI. Page buttons, including a resend button for `unknown` replies.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# 0014. Keep ephemeral transcripts in disposable private storage
|
|
2
|
-
|
|
3
|
-
Status: superseded by [0018](0018-tasks-without-prompt-api.md) (2026-10-05). Was: proposed (2026-10-04)
|
|
4
|
-
|
|
5
|
-
**Context.** The retained HTTP `ephemeral:true` contract removes an isolated session after its queued work finishes. Durable 1.0.2 has reset/compaction but no public single-conversation deletion API. Reset hides old context from the model; it does not erase stored prompts, tool results or child conversations.
|
|
6
|
-
|
|
7
|
-
**Example.** An automated task reads private files. Its final answer may stay in the destination chat, but its private task transcript must not remain searchable in the ordinary conversation store after cleanup.
|
|
8
|
-
|
|
9
|
-
**Decision proposed for owner acceptance.** Give each active ephemeral `(chatGuid, sessionKey)` its own private JSONL storage and native Harness. Let Durable own that storage's inbox, dedupe, steering, tasks and recovery. The main store keeps only opaque identity/lifecycle metadata needed to locate it and external send receipts, not its private prompt/tool transcript. Admission success follows persisted private admission. On restart, reopen recorded private storage and resume its native work; never replay a whole turn in the host. Deliver completed answers with stable main-store receipts. Once the private Harness is idle and its completed answers have an authoritative sent/unknown disposition, close it and remove its directory with restart-safe cleanup. A later batch with the same key starts a fresh private context. Do not remove an active directory or silently lose queued work.
|
|
10
|
-
|
|
11
|
-
**Consequences.** Extra storage/lifecycle wiring is necessary for the old privacy promise, but no second model loop or mirrored inbox. Repeated request IDs and uncertain sends need checks across cleanup/restart. HTTP ephemeral admission remains blocked until this design is accepted, implemented and verified. Ordinary API/cron contexts stay in the shared store.
|
|
12
|
-
|
|
13
|
-
**Rejected.** Calling reset and claiming deletion. Editing shared append-only storage behind Durable. Retaining private transcripts in the main store. Dropping ephemeral behavior without owner approval.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# 0015. Ordinary `/stop` keeps the native inbox
|
|
2
|
-
|
|
3
|
-
Status: superseded by [0018](0018-tasks-without-prompt-api.md) (2026-10-05). Was: proposed (2026-10-04). Replaces only the no-active-run `/stop` clause of proposed [0012](0012-run-in-owned-conversation.md) if accepted. Does not approve its other behavior changes.
|
|
4
|
-
|
|
5
|
-
**Problem.** Ordinary `/stop` must cancel current work and foreground children, suppress unclaimed old replies, and preserve later unprocessed ordinary messages. `Conversation.abort()` in Durable 1.0.2 cancels the conversation's foreground scope but withdraws queued inputs. Looking up `LiveDoc.run.taskId` and calling `abortTask()` preserves the parent inbox, but is not equivalent: generation hands over to a new conversation-owned task between turns. The old task can become terminal between lookup and abort; aborting it then does nothing to the new generation.
|
|
6
|
-
|
|
7
|
-
**Example.** A request reads a file and continues into another model turn. A later ordinary message is queued. `/stop` must cancel the ongoing request, even at that turn boundary, without losing or resubmitting the later message.
|
|
8
|
-
|
|
9
|
-
**Decision.**
|
|
10
|
-
|
|
11
|
-
- Preserve this ordinary-chat behavior; do not silently replace it with “nothing is running” or queue withdrawal.
|
|
12
|
-
- Require a supported native conversation-scope cancellation operation that atomically selects current foreground work while retaining the root chat's queued inputs. Cancellation of work owned below that scope must still follow Durable's ownership rules. The operation must define admission during cancellation and behavior across generation handover.
|
|
13
|
-
- Use the native operation directly. After cancellation settles, suppress old unclaimed drafts using existing delivery records; never change `sending`, `sent` or `unknown` receipts or claim that a started send was undone.
|
|
14
|
-
- If preserved inputs require a new boundary, submit one passive cancellation note through the public conversation API. Durable places existing inbox items; the host never copies their content, creates replacement input submissions, or replays the cancelled request.
|
|
15
|
-
- Keep `/run` cancellation and isolated API/cron/background/health sessions separately scoped. This decision does not accept dropping queued run messages or cancelling unrelated sessions.
|
|
16
|
-
|
|
17
|
-
**API boundary.** Durable 1.0.2 exposes no retain-inbox option on `Conversation.abort()`, and its public transaction API has no atomic task-abort operation. An upstream API change and dependency upgrade are required before this design can be implemented as specified. Do not patch installed package files or write the built-in live/inbox documents to simulate that operation.
|
|
18
|
-
|
|
19
|
-
**Evidence.** The [inbox example](https://github.com/earendil-works/pi/blob/main/packages/durable/test/examples/20-inbox.ts), installed `harness/generation.js` (`createGeneration`, `handOver`), `harness/scheduler.js` (`abort`, `abortConversation`) and public `Tx` declarations establish the distinction. A bounded faux-provider experiment reproduces generation-ID handover and the stale-ID abort no-op; aborting the current generation preserves the queued input, and a passive write places it without a replacement submission. That experiment does not establish a race-free host cancellation implementation.
|
|
20
|
-
|
|
21
|
-
**Rejected.** Repeated lookup/abort polling, a host cancellation epoch or mirrored inbox, withdrawing and resubmitting queued inputs, direct edits of Durable internals, and treating a cancelled wait as cancelled work. Also rejected: claiming a single generation-task abort cancels a whole multi-turn request.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# 0016. Initialize one process-owned HTTP transport
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-05; owner accepted 2026-10-05)
|
|
4
|
-
|
|
5
|
-
**Problem.** Pi's global `httpProxy` and `httpIdleTimeoutMs` settings need HTTP transport setup, not just request-option forwarding. Coding-agent's experimental Durable startup calls `applyHttpProxySettings()` and `configureHttpDispatcher()`, but neither is a public export of coding-agent 1.0.2. Merely assigning `HTTP_PROXY` and `HTTPS_PROXY` after Node starts does not reconfigure the dispatcher created by `--use-env-proxy`.
|
|
6
|
-
|
|
7
|
-
**Example.** An operator sets `httpProxy` in Pi settings without shell proxy variables. The service must use that proxy for authentication and model HTTP requests. Setting the variables at service startup can otherwise leave fetch connecting directly. A provider request timeout also does not replace a socket's inactivity timeout.
|
|
8
|
-
|
|
9
|
-
**Decision.**
|
|
10
|
-
|
|
11
|
-
- The host owns one process-wide HTTP setup, initialized before opening model/auth runtime. Individual chats and private Harness instances do not install global dispatchers.
|
|
12
|
-
- Use public Undici APIs, with an exact direct dependency matching the supported SDK implementation, rather than importing private coding-agent files. Supply the global Pi proxy setting only as environment defaults; retain explicit environment proxy values, lowercase precedence and NO_PROXY behavior. Do not accept project-local proxy overrides where Pi accepts only global configuration.
|
|
13
|
-
- Configure native HTTP headers/body idle timeouts from Pi settings, including disabled timeout semantics. Keep transport fetch and dispatcher on the same Undici implementation so compressed responses are decoded consistently. Preserve deliberately supplied fetch implementations. Follow the SDK's connection-family and client-error handling rather than inventing transport retries.
|
|
14
|
-
- `/reload` may replace transport for subsequent requests without cancelling active work. Track transport resources in memory only, let active requests finish on their original clients, and retire old clients safely. During shutdown, stop producers and Harness work before releasing transport; restore globals only if they are still ours. Never close a dispatcher owned by another caller.
|
|
15
|
-
- Durable/provider retry policy and request timeout options remain unchanged. This setup is not a host activity timer, prompt deadline, compaction deadline, inbox or execution loop.
|
|
16
|
-
|
|
17
|
-
**Consequences.** Process-global HTTP setup belongs to application lifecycle, not per-conversation configuration. Standalone callers with an injected runtime/fetch keep control of their transport. The implementation must verify proxy precedence/bypass, compressed responses, idle timeout enable/disable, reload with active requests, cleanup and custom-fetch preservation against loopback fixtures before claiming compatibility.
|
|
18
|
-
|
|
19
|
-
**Evidence.** The installed coding-agent `core/http-dispatcher.js` and experimental [harness setup](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/experimental/durable/harness-setup.ts) use Undici EnvHttpProxyAgent and matching fetch globals. A bounded Node 26 loopback experiment confirms late environment defaults leave built-in fetch direct, while public Undici 8.10.2 dispatcher plus matching fetch routes through the configured proxy. This establishes an implementation path, not completed lifecycle/timeout behavior or real-provider connectivity.
|
|
20
|
-
|
|
21
|
-
**Rejected.** Environment mutation as proof of effective transport, request timeout as proof of socket-idle enforcement, private SDK imports, patching installed packages, a new HTTP retry algorithm, and relying on newer Node-only proxy-reset APIs while retaining Node >=22.22.2 support.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# 0017. Invoke browser CLI in a native conversation-owned scope
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-05; owner accepted 2026-10-05). Replaces only the browser-through-unrestricted-bash part of proposed [0004](0004-native-extensions.md) if accepted.
|
|
4
|
-
|
|
5
|
-
**Problem.** `PI_BROWSER_HOME` scopes the pi-browser wrapper's profiles/output, not Playwright CLI's daemon registry. CLI 0.1.19 keeps that registry in the user's cache directory, derives daemon workspace identity from `.playwright` or the installed package, and enumerates other workspaces. All chats currently share the same shell environment and working directory. A named session or separate profile directory alone therefore does not preserve the old browser adapter's private-session rules.
|
|
6
|
-
|
|
7
|
-
**Example.** Two chats both use `-s=task`. Even with different PI_BROWSER_HOME values, they can resolve the same daemon/session and see other registry entries. Giving the model a suggested name is not enforcement.
|
|
8
|
-
|
|
9
|
-
**Decision.**
|
|
10
|
-
|
|
11
|
-
- Offer a thin native Durable browser tool that invokes the installed, pinned pi-browser CLI by argv. Do not load an SDK browser extension, emulate ExtensionAPI, vendor CLI internals or implement another browser driver.
|
|
12
|
-
- Derive browser scope from canonical storage identity plus native conversation identity, not a model-selected name. Ordinary chats, runs and isolated API/cron contexts get distinct scope; disallowed read-only contexts do not get the tool.
|
|
13
|
-
- Give only browser child processes a dedicated HOME, profile/output root and scoped working directory. Leave ordinary coding tools' HOME/cwd unchanged. Use public process/CLI controls rather than PWTEST daemon/socket environment variables. Verify daemon discovery and attachment cannot escape the scope, including environment-inherited external browser targets.
|
|
14
|
-
- Always select the owned session/profile and headless mode. Deny attachment/discovery, global close/kill, arbitrary profile/config paths and headed overrides, retaining the old service's restrictions. Expose the ordinary browsing operations needed by the product through validated arguments; operation results remain untrusted data.
|
|
15
|
-
- Close owned browsers at the agreed stop/shutdown boundaries without touching unrelated sessions. Track only external browser resources and cleanup facts, not a second task state machine. Define restart cleanup and privacy retention before claiming isolation. Do not silently delete saved login profiles or replay uncertain browser mutations.
|
|
16
|
-
- Do not mistake this tool boundary for an OS sandbox: unrestricted bash/read can still access host files and invoke other executables. State that limit explicitly; stronger containment requires a separate decision.
|
|
17
|
-
|
|
18
|
-
**Consequences.** Native tool registration replaces browser instructions as the enforcement boundary for normal product use. The browser skill can still explain workflows, but cannot be the sole privacy mechanism. Actual Chrome/CLI fixtures must verify same-name sessions in different scopes, registry isolation, profile/config rejection and owned cleanup before release.
|
|
19
|
-
|
|
20
|
-
**Evidence.** The installed wrapper source separates PI_BROWSER_HOME from Playwright's registry. In a temporary copy installed with the locked manifests (`npm ci --ignore-scripts`), CLI 0.1.19's `cli-client/registry.js` uses OS HOME/cache, scans every workspace and exposes only a PWTEST-specific daemon-root override. A bounded temporary-HOME experiment confirms different PI_BROWSER_HOME values share the daemon directory and registry enumeration sees another synthetic workspace. No browser or personal profile was opened. The experiment describes the pinned implementation; production must not import those private files.
|
|
21
|
-
|
|
22
|
-
**Rejected.** Profile-directory changes or prompt-only session naming as proof of isolation, private PWTEST controls, changing HOME/cwd for all bash tools, SDK extension adaptation, unscoped global browser cleanup and claiming unrestricted coding tools are sandboxed.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# 0018. Ordinary `/stop`; scheduled tasks without HTTP `/prompt` and `/send`
|
|
2
|
-
|
|
3
|
-
Status: accepted (2026-10-05; owner accepted 2026-10-05). Supersedes proposed [0014](0014-ephemeral-private-storage.md) and [0015](0015-ordinary-stop-keeps-native-inbox.md). Replaces the HTTP `/prompt` clause of proposed [0012](0012-run-in-owned-conversation.md) and the `/send` resend hint of [0003](0003-replies-at-most-once.md).
|
|
4
|
-
|
|
5
|
-
**Context.** Every production caller of HTTP `/prompt` and `/send` is a script or cron job on the same machine, plus the agent sending files through curl. The work they start runs in a separate context, but its result never reaches the chat's own conversation: when the user replies to a morning summary, the chat does not know what was sent. 0014 and 0015 wait for storage lifecycle work and an upstream Durable API.
|
|
6
|
-
|
|
7
|
-
**Example.** At 08:00 a cron job runs a script that builds the newsletter prompt; the agent works in its own conversation and sends five links. The user replies "expand the third one". The chat conversation must already hold the five links, not the prompt or tool calls that produced them.
|
|
8
|
-
|
|
9
|
-
**Decision.**
|
|
10
|
-
|
|
11
|
-
- **Ordinary `/stop`** (no active `/run`) aborts the generation task that owns the current inputs (`LiveDoc.run`), not the conversation, so later queued messages stay in the inbox. If generation hands the same inputs to a new task meanwhile, abort that one too. Then write a passive `[/stop]` entry so Durable places the queued messages. `/stop` during a run is unchanged.
|
|
12
|
-
- **Tasks.** Cron `prompt` jobs, `schedule_task` firings and background summaries each run in a fresh ownerless conversation, keyed by request ID. Nothing carries over between runs; continuity lives in the chat conversation and memory. Transcripts are kept, not deleted. The model health check reuses one conversation.
|
|
13
|
-
- **The chat records what was sent.** A delivered task answer, and every service or tool send (reminders, cron `send`, background summaries, Automation, `send_message`), is written to the chat conversation as one passive entry with its delivery status, deduplicated by request ID. No extra model turn; the chat's next turn sees it.
|
|
14
|
-
- **No HTTP `/prompt`, `/send` or `ephemeral`.** Cron `send` and `prompt` actions accept an optional `command` (absolute argv). Its stdout replaces the text or prompt; empty output skips the run. Scripts print instead of POSTing, and are scheduled in `cron/jobs.json`. The agent sends files and extra messages with the native `send_message` tool, using the existing direct-send receipts (tool task ID as request ID). Remaining HTTP: read-only pages, reminders, cron management and model health.
|
|
15
|
-
- **Cutover.** The old service still needs `/prompt` and `/send`, so production scripts, crontab entries and `jobs.json` change in the same step as the cutover ([0009](0009-one-shot-cutover.md)).
|
|
16
|
-
|
|
17
|
-
**Consequences.** One way to start agent work from outside a chat, and one place (the chat conversation) that knows what the user saw. `/stop` uses a bounded lookup/abort retry instead of an atomic native operation. Old `ephemeral` deletion is dropped; private tool output stays searchable under `durable/`. External callers on other machines can no longer prompt the agent; a future multi-entry design decides that. The shared scheduler's `jobs.json` parser gains the optional `command` field.
|
|
18
|
-
|
|
19
|
-
**Rejected.** Handing task results to the chat as input (an extra model turn, interrupts current work, rewrites the sent text). Private per-session storage (0014). Waiting for an upstream cancellation API (0015). Keeping `/prompt` and `/send` as thin HTTP entries.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# 0019. Remove scheduled services from the chat application
|
|
2
|
-
|
|
3
|
-
Status: proposed
|
|
4
|
-
|
|
5
|
-
**Problem.** The owner wants scheduling removed from this project and will design
|
|
6
|
-
it separately later. Keeping empty job files or startup switches still leaves
|
|
7
|
-
scheduled effects and their dependencies in the chat application's lifecycle.
|
|
8
|
-
|
|
9
|
-
**Example.** Starting messaging must not create reminder databases, register cron
|
|
10
|
-
jobs, run PT checks or watch completion files, even when old configuration exists.
|
|
11
|
-
|
|
12
|
-
**Decision.**
|
|
13
|
-
|
|
14
|
-
- Remove cron, reminders, delayed/repeating prompts, background completion
|
|
15
|
-
watchers/summaries and PT Automation from source, tools, routes, pages, startup,
|
|
16
|
-
preflight and direct dependencies. Do not replace them with disabled stubs.
|
|
17
|
-
- Keep messaging, immediate `send_message`, foreground subagents, `/run`, ordinary
|
|
18
|
-
`/stop`, native task inspection and explicit tool-less model health checks.
|
|
19
|
-
Keep the six-hour context compaction required by ADR 0008; it is a chat
|
|
20
|
-
maintenance timer, not user scheduling.
|
|
21
|
-
- Keep existing settings, memory, history, receipts and offline backup/import
|
|
22
|
-
data intact. Removal does not authorize deleting stored job data, modifying
|
|
23
|
-
installed Pi extensions, or changing the operating system's crontab/launchd.
|
|
24
|
-
This application does not manage or stop independent operating-system jobs.
|
|
25
|
-
- Removed scheduling and Automation endpoints return 404. There is no
|
|
26
|
-
compatibility adapter or automatic migration to another scheduler.
|
|
27
|
-
- Retain historical isolated-conversation metadata for read-only inspection,
|
|
28
|
-
but do not deliver answers from retired scheduled conversations.
|
|
29
|
-
|
|
30
|
-
This proposal supersedes the scheduled-service clauses of ADR 0013 and ADR 0018
|
|
31
|
-
only when accepted. Their ordinary stop, immediate sending and receipt decisions
|
|
32
|
-
remain unchanged. Accepted ADR files are not edited. Scope and implementation
|
|
33
|
-
progress are tracked in GitHub #33, not in this document.
|
|
34
|
-
|
|
35
|
-
**Consequences.** A future scheduler needs its own design and approval. Startup
|
|
36
|
-
still needs independent old producers to be reviewed, and model/Messages live
|
|
37
|
-
acceptance remains separate from removal of scheduled services.
|
|
38
|
-
|
|
39
|
-
**Rejected.** Merely clearing job definitions, hiding tools while retaining
|
|
40
|
-
workers, reviving HTTP `/prompt` or `/send`, and deleting production state as
|
|
41
|
-
part of a source-code change.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# 0020. Report native task calls in iMessage
|
|
2
|
-
|
|
3
|
-
Status: accepted (owner approved the format, exclusions, implementation and restart)
|
|
4
|
-
|
|
5
|
-
**Problem.** Final-only replies leave the owner unable to see tool and task
|
|
6
|
-
progress in iMessage while work is running.
|
|
7
|
-
|
|
8
|
-
**Example.** A search emits `[tool] web_search · query=上海浦东 私立医院`
|
|
9
|
-
before the final answer. Other custom tasks emit their native task name. A
|
|
10
|
-
foreground subagent's calls use `[subagent/tool]` and go to its parent chat.
|
|
11
|
-
|
|
12
|
-
**Decision.**
|
|
13
|
-
|
|
14
|
-
- Observe committed native task admission with Durable's public commit API;
|
|
15
|
-
do not wrap tools, poll task phases or introduce another execution loop.
|
|
16
|
-
- With `settings.json` setting `progressMessages: true`, send one plain-text
|
|
17
|
-
notice per new task to its owning, reply-enabled chat, except `pi.generation`
|
|
18
|
-
which the owner excludes. A `pi.tool` task is one tool notice, not a duplicate
|
|
19
|
-
task notice. Keep IDs internal for deduplication; do not display them.
|
|
20
|
-
- Include native names, parent names and bounded argument summaries, not tool
|
|
21
|
-
outputs, thinking, free-form commands, prompts or document contents. Path and
|
|
22
|
-
search-query previews are screened for known credential indicators; URL
|
|
23
|
-
previews omit user info, paths, queries and fragments. Arbitrary user-authored
|
|
24
|
-
secrets cannot be guaranteed detectable.
|
|
25
|
-
- Follow Durable's conversation ownership for child/subagent attribution.
|
|
26
|
-
Health and unrelated ownerless conversations stay local.
|
|
27
|
-
- Use existing direct-send receipts keyed by task ID. Unknown sends are not
|
|
28
|
-
retried. Do not replay pre-existing live tasks on startup or generate model
|
|
29
|
-
context entries for progress notices.
|
|
30
|
-
- Notifications do not gate task execution; drain admitted notices before
|
|
31
|
-
automatic final replies. Stop observation and join pending sends on shutdown.
|
|
32
|
-
- Missing or false configuration retains final-only delivery. No stored
|
|
33
|
-
settings or receipt formats are removed.
|
|
34
|
-
|
|
35
|
-
**Consequences.** iMessage can receive many notices. Progress reflects task
|
|
36
|
-
admission, not confirmed completion. Final answers retain their existing
|
|
37
|
-
reconciliation and at-most-once rules. This expands final-only chat behavior;
|
|
38
|
-
accepted ADR files remain unchanged. Scope and validation belong in GitHub #33.
|
|
39
|
-
|
|
40
|
-
**Rejected.** Asking the model to narrate every call, forwarding raw outputs,
|
|
41
|
-
replaying old task history, or using unreliable delivery confirmation to retry.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# 0021. Readable progress without automatic link previews
|
|
2
|
-
|
|
3
|
-
Status: accepted (owner approved implementation and restart on 2026-10-05)
|
|
4
|
-
|
|
5
|
-
Supersedes the argument-display details of ADR 0020; routing, admission,
|
|
6
|
-
receipts, exclusions and recovery remain unchanged.
|
|
7
|
-
|
|
8
|
-
**Problem.** `command=[128 chars]` does not explain the work. Clickable URLs
|
|
9
|
-
in progress and default answers create unwanted iMessage preview cards.
|
|
10
|
-
|
|
11
|
-
**Example.** `[tool] bash · 搜索上海天气 · timeout=30` reports intent without
|
|
12
|
-
revealing a shell command. `url=weather.com.cn` identifies a source without
|
|
13
|
-
a clickable URL.
|
|
14
|
-
|
|
15
|
-
**Decision.**
|
|
16
|
-
|
|
17
|
-
- Add an optional short `description` to the native bash schema. Ask the model
|
|
18
|
-
for a non-sensitive purpose in the user's language; delegate execution,
|
|
19
|
-
timeout validation and output handling unchanged to native bash.
|
|
20
|
-
- Show the screened, bounded description instead of the command or its length.
|
|
21
|
-
Missing or credential-like descriptions use `执行命令`. Raw commands remain
|
|
22
|
-
private. Descriptions report intent, not verified completion.
|
|
23
|
-
- URL arguments show only hostnames with fullwidth dots, never schemes,
|
|
24
|
-
user information, paths, queries or fragments. Screened path/query previews
|
|
25
|
-
omit explicit HTTP(S) URLs.
|
|
26
|
-
- Default answers name sources without URLs. Provide original links when the
|
|
27
|
-
user asks. This is prompt policy, not a transport-level preview toggle or
|
|
28
|
-
a prohibition on explicitly requested links.
|
|
29
|
-
- Credential screening is best effort; arbitrary secrets in model-authored
|
|
30
|
-
labels cannot be guaranteed detectable.
|
|
31
|
-
|
|
32
|
-
**Rejected.** Raw shell snippets, another model call to summarize commands,
|
|
33
|
-
transport retries, and claiming an unsupported Messages preview-disable option.
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# 0022. Arrow and settlement progress notices
|
|
2
|
-
|
|
3
|
-
Status: accepted (owner requested arrow/checkmark style and omitted timeout on 2026-10-05)
|
|
4
|
-
|
|
5
|
-
Supersedes the admission-only notification/display decisions of ADR 0020 and
|
|
6
|
-
the displayed timeout example in ADR 0021. Other safety and routing decisions
|
|
7
|
-
remain unchanged.
|
|
8
|
-
|
|
9
|
-
**Problem.** Character counts and bracketed tool headers are noisy. The owner
|
|
10
|
-
prefers the earlier arrow/checkmark style and does not need timeout parameters
|
|
11
|
-
in chat.
|
|
12
|
-
|
|
13
|
-
**Example.** `→ bash: 搜索上海天气`, then `✓ bash (0.8s)` on successful settlement.
|
|
14
|
-
An unsuccessful call gets `× bash (0.8s)`, never a success checkmark.
|
|
15
|
-
|
|
16
|
-
**Decision.**
|
|
17
|
-
|
|
18
|
-
- Observe native committed task admission and terminal settlement through the
|
|
19
|
-
existing public commit subscription, without another execution loop.
|
|
20
|
-
- Emit one arrow at admission and one checkmark/cross at settlement for tasks
|
|
21
|
-
admitted during this process lifetime. Exclude generation tasks and retain
|
|
22
|
-
native parent/child ownership, allowlist and private-health routing.
|
|
23
|
-
- For native tools, success requires both a completed task outcome and the
|
|
24
|
-
stored tool-result message having no error flag. Aborted, failed and missing
|
|
25
|
-
or error tool results are not success.
|
|
26
|
-
- Duration is local elapsed admission-to-settlement time, including queueing;
|
|
27
|
-
it is not exact subprocess-only runtime or recipient delivery latency.
|
|
28
|
-
- Keep separate direct-send receipt IDs for admission and settlement. Do not
|
|
29
|
-
replay historical starts/completions or retry uncertain sends after restart.
|
|
30
|
-
- Omit timeout from displayed argument summaries only; native enforcement is
|
|
31
|
-
unchanged. Keep short screened purpose labels and non-clickable hostnames,
|
|
32
|
-
never raw commands or outputs.
|
|
33
|
-
- Final replies drain already queued progress notices. Progress remains
|
|
34
|
-
optional through the existing setting. This intentionally doubles most
|
|
35
|
-
task progress notices compared with admission-only reporting.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# 0023. Retain progress type prefixes
|
|
2
|
-
|
|
3
|
-
Status: accepted (owner explicitly corrected the format on 2026-10-05)
|
|
4
|
-
|
|
5
|
-
Supersedes the prefix-free display examples of ADR 0022 only. Admission,
|
|
6
|
-
settlement, privacy, elapsed time and receipt/recovery behavior are unchanged.
|
|
7
|
-
|
|
8
|
-
**Problem.** Arrows and checkmarks replaced useful type labels. The owner wants
|
|
9
|
-
bracketed prefixes retained alongside the new status markers.
|
|
10
|
-
|
|
11
|
-
**Example.** `[tool] → bash: 搜索上海天气`, followed by `[tool] ✓ bash (0.8s)`.
|
|
12
|
-
Owned child tools use `[subagent/tool] → read: path=…` and
|
|
13
|
-
`[subagent/tool] ✓ read (0.8s)`. Native non-tool tasks retain `[task]` and
|
|
14
|
-
parent-qualified task prefixes. Unsuccessful settlement uses `×`.
|
|
15
|
-
|
|
16
|
-
**Decision.** Render the native type/parent prefix first, then the status marker,
|
|
17
|
-
then the native tool/task name and short safe summary or elapsed time. Do not
|
|
18
|
-
restore timeout, raw shell commands or clickable URLs.
|