@timqi/pier 0.0.1 → 0.0.3
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 +87 -12
- package/dist/agent/config.js +273 -27
- package/dist/agent/credentials.js +18 -12
- package/dist/agent/events.js +5 -41
- package/dist/agent/models.js +12 -0
- package/dist/agent/pi.js +182 -27
- package/dist/boards/boards.js +20 -10
- package/dist/channels/routes.js +1 -1
- package/dist/channels/runtime.js +36 -5
- package/dist/channels/slack-api.js +2 -4
- package/dist/channels/slack-outbound.js +4 -8
- package/dist/channels/slack-render.js +1 -4
- package/dist/channels/slack-tool.js +28 -3
- package/dist/channels/slack.js +20 -9
- package/dist/channels/telegram-api.js +3 -4
- package/dist/channels/telegram.js +37 -28
- package/dist/cli.js +177 -29
- package/dist/core/hub.js +36 -5
- package/dist/core/identity.js +5 -0
- package/dist/core/inbound-file.js +70 -0
- package/dist/core/inbox.js +32 -0
- package/dist/core/queue.js +9 -3
- package/dist/core/reply.js +20 -5
- package/dist/core/router.js +200 -14
- package/dist/core/types.js +53 -0
- package/dist/db.js +54 -8
- package/dist/drain.js +145 -0
- package/dist/main.js +180 -18
- package/dist/secrets.js +10 -6
- package/dist/service.js +192 -18
- package/dist/settings.js +77 -8
- package/dist/tasks/agent.js +41 -5
- package/dist/tasks/callbacks.js +29 -89
- package/dist/tasks/definitions.js +2 -6
- package/dist/tasks/execution.js +10 -1
- package/dist/tasks/groups.js +20 -49
- package/dist/tasks/messages.js +106 -21
- package/dist/tasks/outbox.js +157 -0
- package/dist/tasks/routes.js +6 -4
- package/dist/tasks/service.js +92 -22
- package/dist/tasks/store.js +48 -55
- package/dist/tasks/tool.js +19 -4
- package/dist/tasks/types.js +7 -0
- package/dist/update.js +146 -0
- package/dist/web/auth.js +89 -26
- package/dist/web/explorer.js +147 -0
- package/dist/web/files.js +28 -12
- package/dist/web/instance.js +165 -0
- package/dist/web/provider-flows.js +249 -0
- package/dist/web/providers.js +141 -0
- package/dist/web/public/assets/index-cCIuQnDr.css +2 -0
- package/dist/web/public/assets/index-fASxMPr6.js +90 -0
- package/dist/web/public/icon-192.png +0 -0
- package/dist/web/public/icon-32.png +0 -0
- package/dist/web/public/icon-512.png +0 -0
- package/dist/web/public/icon-maskable-512.png +0 -0
- package/dist/web/public/icon-touch-192.png +0 -0
- package/dist/web/public/icon.svg +29 -11
- package/dist/web/public/index.html +50 -32
- package/dist/web/server.js +110 -120
- package/docs/deploy.md +142 -64
- package/package.json +1 -1
- package/skills/pier-boards/SKILL.md +16 -7
- package/skills/pier-help/SKILL.md +110 -0
- package/skills/pier-slack/SKILL.md +20 -3
- package/skills/pier-tasks/SKILL.md +19 -12
- package/dist/web/public/assets/index-8CinH1uR.css +0 -2
- package/dist/web/public/assets/index-DAgP1Gq8.js +0 -78
- package/dist/web/public/sw.js +0 -21
package/docs/deploy.md
CHANGED
|
@@ -24,17 +24,15 @@ you want the unit to say something different.
|
|
|
24
24
|
## Prerequisites
|
|
25
25
|
|
|
26
26
|
- Node 24 or newer (`node:sqlite` is used unflagged).
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
The unit below runs the build output, so a deploy is always "update the
|
|
37
|
-
checkout, rebuild, restart" — never "run from source".
|
|
27
|
+
- A user-writable global npm prefix. The updater runs as you, so an initial
|
|
28
|
+
install that needed `sudo npm install -g` cannot later update itself.
|
|
29
|
+
- The `sqlite3` CLI is optional, for the off-machine backup and password steps
|
|
30
|
+
below. Pier itself and its automatic update backup do not need it.
|
|
31
|
+
- Pier installed globally: `npm install -g @timqi/pier`. The unit runs that
|
|
32
|
+
installed entry point, so a deploy is `pier update`. A checkout
|
|
33
|
+
(`git clone` + `npm ci && npm run build`) is the *develop* path; point the
|
|
34
|
+
unit's `ExecStart` at its `dist/main.js` if you run one as the service, and
|
|
35
|
+
update it with the "From a checkout" steps under Updating.
|
|
38
36
|
|
|
39
37
|
## The unit
|
|
40
38
|
|
|
@@ -44,7 +42,7 @@ or as a dedicated system user means an agent that cannot touch the files you
|
|
|
44
42
|
wanted it to work on.
|
|
45
43
|
|
|
46
44
|
`~/.config/systemd/user/pier.service` — what `pier service install` generates,
|
|
47
|
-
with your
|
|
45
|
+
with your absolute Node and package paths filled in:
|
|
48
46
|
|
|
49
47
|
```ini
|
|
50
48
|
[Unit]
|
|
@@ -55,29 +53,33 @@ Wants=network-online.target
|
|
|
55
53
|
|
|
56
54
|
[Service]
|
|
57
55
|
Type=simple
|
|
58
|
-
WorkingDirectory=%h
|
|
59
|
-
#
|
|
60
|
-
# installed by nvm/fnm/asdf is not on it
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
WorkingDirectory=%h
|
|
57
|
+
# Absolute paths on purpose: systemd starts with a minimal PATH, so a node
|
|
58
|
+
# installed by nvm/fnm/asdf is not on it — the installer fills in the node
|
|
59
|
+
# that installed Pier and the globally installed entry point.
|
|
60
|
+
ExecStart="/absolute/path/to/node" "/absolute/npm/prefix/lib/node_modules/@timqi/pier/dist/main.js"
|
|
61
|
+
Environment="NODE_ENV=production"
|
|
63
62
|
# Loopback by default. Put a reverse proxy in front before widening this —
|
|
64
63
|
# whoever reaches this port can drive an agent that runs a shell.
|
|
65
|
-
Environment=HOST=127.0.0.1
|
|
66
|
-
Environment=PORT=3141
|
|
67
|
-
# Where the database, the boards and the generated password hash live.
|
|
68
|
-
Environment=PIER_HOME=%h/.pier
|
|
64
|
+
Environment="HOST=127.0.0.1"
|
|
65
|
+
Environment="PORT=3141"
|
|
69
66
|
Restart=always
|
|
70
67
|
RestartSec=2
|
|
71
|
-
# The journal is where the first-run password is printed, so keep it readable.
|
|
72
68
|
StandardOutput=journal
|
|
73
69
|
StandardError=journal
|
|
74
|
-
# Otherwise every line is tagged "node"; this makes `journalctl -t pier` work.
|
|
75
70
|
SyslogIdentifier=pier
|
|
76
71
|
|
|
77
72
|
[Install]
|
|
78
73
|
WantedBy=default.target
|
|
79
74
|
```
|
|
80
75
|
|
|
76
|
+
`--pier-home` adds a quoted `PIER_HOME` environment line. Paths with spaces and
|
|
77
|
+
literal systemd `%` specifiers are escaped. `pier service install` also writes an
|
|
78
|
+
updater unit containing the exact npm executable currently on `PATH`. Re-run with
|
|
79
|
+
`--force` after changing service settings or the Node/npm installation; it
|
|
80
|
+
rewrites both units and restarts the running service. The limits drop-in remains
|
|
81
|
+
operator-owned and is never overwritten.
|
|
82
|
+
|
|
81
83
|
Enable it, and tell logind to keep your user manager alive after you log out —
|
|
82
84
|
without lingering, every scheduled task stops when your SSH session ends:
|
|
83
85
|
|
|
@@ -167,7 +169,9 @@ journalctl --user -u pier --since -1h | grep 'tasks:' # one area
|
|
|
167
169
|
```
|
|
168
170
|
|
|
169
171
|
Every line is `area: message` — `core`, `agent`, `tasks`, `slack`, `telegram`,
|
|
170
|
-
`channels`, `
|
|
172
|
+
`channels`, `slack.tool`, `auth`, `boards`, `client`, `db`, `drain`, `secrets`,
|
|
173
|
+
`settings`, `credentials`, `update`, `web.providers`, `pier` — so an area is a
|
|
174
|
+
grep
|
|
171
175
|
and a level is a `-p`. The level reaches journald as a syslog priority prefix, which Pier
|
|
172
176
|
emits only when systemd says the output is a journal (`$JOURNAL_STREAM`); run
|
|
173
177
|
in a terminal, the same lines carry a timestamp and a level word instead.
|
|
@@ -214,42 +218,105 @@ new password is generated and printed:
|
|
|
214
218
|
|
|
215
219
|
```sh
|
|
216
220
|
sqlite3 ~/.pier/db/pier.db 'DELETE FROM auth'
|
|
217
|
-
|
|
221
|
+
pier restart
|
|
218
222
|
```
|
|
219
223
|
|
|
220
224
|
Changing the password invalidates every session cookie: the cookies are signed
|
|
221
225
|
with the stored hash.
|
|
222
226
|
|
|
227
|
+
## Restarting and reloading
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
pier restart # finish active work, then restart the service
|
|
231
|
+
pier reload # apply channel config and recycle idle sessions in place
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Both commands signal the installed systemd service; they are not foreground
|
|
235
|
+
process controls. `pier restart` refuses new messages and root Task runs, waits
|
|
236
|
+
up to five minutes for active work, then exits for `Restart=always` to start the
|
|
237
|
+
next process. At the deadline it records every aborted IM turn first, and the
|
|
238
|
+
next process posts that note after its adapter starts. Cleanup after the deadline
|
|
239
|
+
has one shared 10-second bound regardless of how many sessions are stuck.
|
|
240
|
+
|
|
241
|
+
`pier reload` does not stop active work. It reloads Slack and Telegram adapters
|
|
242
|
+
and immediately evicts idle sessions nobody is watching, so their next message
|
|
243
|
+
opens with current agent files and configuration. Streaming sessions and
|
|
244
|
+
sessions held by an open workbench stay attached until their normal eviction.
|
|
245
|
+
|
|
246
|
+
An ordinary `systemctl --user restart pier` and `pier update` remain fast,
|
|
247
|
+
hard-stop paths. Let active work finish first when using either one.
|
|
248
|
+
|
|
223
249
|
## Updating
|
|
224
250
|
|
|
225
|
-
|
|
251
|
+
```sh
|
|
252
|
+
pier update # installs the latest release; hard-stops/restarts Pier
|
|
253
|
+
pier update --check # only says whether one exists
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The workbench footer says the same thing without being asked: the server checks
|
|
257
|
+
`registry.npmjs.org` every six hours in the background, and the version turns
|
|
258
|
+
into `v0.0.1 → 0.0.2` when there is something newer. A failed check is silent
|
|
259
|
+
by design — an offline box is not a broken one.
|
|
260
|
+
|
|
261
|
+
From a checkout instead, stop and back up before replacing the build:
|
|
226
262
|
|
|
227
263
|
```sh
|
|
264
|
+
systemctl --user stop pier
|
|
265
|
+
pier backup
|
|
228
266
|
cd ~/pier
|
|
229
|
-
git fetch --tags && git checkout v0.2
|
|
267
|
+
git fetch --tags && git checkout v0.0.2 # a tag, not a branch
|
|
230
268
|
npm ci && npm run build
|
|
231
|
-
systemctl --user
|
|
269
|
+
systemctl --user start pier
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
For a service install, `pier update` stops Pier first; it does not use the
|
|
273
|
+
graceful `pier restart` path. The Console's **Update now** and the automatic
|
|
274
|
+
path do: both drain (new work refused, running turns finished, the rest
|
|
275
|
+
ledgered for the next boot to report) before the updater unit is started.
|
|
276
|
+
|
|
277
|
+
Either way the updater snapshots the database to
|
|
278
|
+
`~/.pier/db/pier.db.release.bak` before npm touches the package. This happens for
|
|
279
|
+
every release, including releases with no schema change. If installation or
|
|
280
|
+
backup fails, the updater unit still tries to start the previously installed
|
|
281
|
+
service and reports the failure in its journal.
|
|
282
|
+
|
|
283
|
+
### Automatic updates
|
|
284
|
+
|
|
285
|
+
Off by default. Switched on from the version panel, it checks every 15 minutes
|
|
286
|
+
and hands over only when all three hold: the switch is on, a newer release
|
|
287
|
+
exists, and the instance is idle (nothing streaming, no task run in flight).
|
|
288
|
+
systemd only — without the unit there is nothing to hand the install to.
|
|
289
|
+
|
|
290
|
+
The unit records **absolute** paths to the node and npm that installed Pier,
|
|
291
|
+
because systemd's PATH has neither. That pins it to one directory of one version
|
|
292
|
+
manager: with fnm or nvm, `fnm uninstall v24` deletes the Node that `ExecStart`
|
|
293
|
+
names while the running process survives (Linux keeps a deleted binary mapped).
|
|
294
|
+
Pier checks those paths at boot and before every handover, and reports it in the
|
|
295
|
+
journal and in the version panel rather than letting the next restart fail:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
pier service install --force # re-records the current node and npm
|
|
232
299
|
```
|
|
233
300
|
|
|
234
301
|
A newer Pier brings its own schema up on the next start: the migrations run in
|
|
235
302
|
one transaction before the port opens, and the version they leave behind is
|
|
236
|
-
stamped in the database.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
303
|
+
stamped in the database. It also snapshots the immediately preceding schema to
|
|
304
|
+
`~/.pier/db/pier.db.v<N>.bak` (`N` = the schema it was at). **Upgrades only.**
|
|
305
|
+
Start an older Pier on a database a newer one has migrated and it refuses to run
|
|
306
|
+
rather than write tables it does not understand — the way back down is either
|
|
307
|
+
the release backup or that schema snapshot:
|
|
241
308
|
|
|
242
309
|
```sh
|
|
243
310
|
systemctl --user stop pier
|
|
244
|
-
cd ~/.pier/db && rm -f pier.db pier.db-wal pier.db-shm && cp pier.db.
|
|
245
|
-
# then
|
|
311
|
+
cd ~/.pier/db && rm -f pier.db pier.db-wal pier.db-shm && cp pier.db.release.bak pier.db
|
|
312
|
+
# then reinstall the Pier release that created that backup
|
|
246
313
|
```
|
|
247
314
|
|
|
248
|
-
The
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
therefore protect against a bad upgrade, not against a lost disk —
|
|
252
|
-
off-machine copy is still yours to take.
|
|
315
|
+
The release backup is replaced atomically on each update. The three newest
|
|
316
|
+
schema snapshots are also kept and older ones removed as later migrations
|
|
317
|
+
supersede them. Each is a full copy of the database. They sit next to the
|
|
318
|
+
database and therefore protect against a bad upgrade, not against a lost disk —
|
|
319
|
+
an off-machine copy is still yours to take.
|
|
253
320
|
|
|
254
321
|
### Can it update itself?
|
|
255
322
|
|
|
@@ -259,32 +326,33 @@ in `pier.service`'s cgroup, so an update script spawned by Pier dies halfway
|
|
|
259
326
|
through — sometimes after unpacking and before restarting, which is the one
|
|
260
327
|
outcome worse than not updating.
|
|
261
328
|
|
|
262
|
-
So
|
|
329
|
+
So installation writes a second unit and `pier update` starts it after recording
|
|
330
|
+
the running service's effective `PIER_HOME` in a runtime drop-in. That includes
|
|
331
|
+
an operator environment override, so the updater cannot back up one database and
|
|
332
|
+
migrate another. `~/.config/systemd/user/pier-update.service`:
|
|
263
333
|
|
|
264
334
|
```ini
|
|
265
335
|
[Unit]
|
|
266
|
-
Description=Update Pier to the latest
|
|
336
|
+
Description=Update Pier to the latest published version
|
|
267
337
|
|
|
268
338
|
[Service]
|
|
269
339
|
Type=oneshot
|
|
270
|
-
|
|
271
|
-
ExecStart=/
|
|
272
|
-
|
|
340
|
+
ExecStart=systemctl --user stop pier.service
|
|
341
|
+
ExecStart=/path/to/node /path/to/pier/dist/cli.js backup
|
|
342
|
+
ExecStart=/path/to/node /recorded/path/to/npm install -g @timqi/pier@latest
|
|
343
|
+
ExecStopPost=systemctl --user start pier.service
|
|
273
344
|
```
|
|
274
345
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
```sh
|
|
279
|
-
systemctl --user start pier-update.service
|
|
280
|
-
```
|
|
346
|
+
`pier update` triggers that unit with a call that survives Pier's restart because
|
|
347
|
+
the work happens in a different cgroup. Starting the unit directly is unsupported:
|
|
348
|
+
the command first records the effective database home used by the running service.
|
|
281
349
|
|
|
282
350
|
Deliberately **not** a `systemd.timer`. An unattended update is a machine that
|
|
283
|
-
rewrites its own code from the network while holding your API keys, and
|
|
284
|
-
interrupts whatever session was mid-turn
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
351
|
+
rewrites its own code from the network while holding your API keys, and its hard
|
|
352
|
+
stop interrupts whatever session was mid-turn. Pier notices a newer release
|
|
353
|
+
and says so in the workbench footer; starting the update stays a decision someone
|
|
354
|
+
makes. The updater's `ExecStopPost` is what brings the service back after both
|
|
355
|
+
success and failure.
|
|
288
356
|
|
|
289
357
|
## Remote access
|
|
290
358
|
|
|
@@ -294,14 +362,24 @@ elsewhere, pick a tunnel rather than a wider bind:
|
|
|
294
362
|
- `ssh -L 3141:localhost:3141 server` — nothing to configure, nothing exposed.
|
|
295
363
|
- Tailscale, or Cloudflare Tunnel — no open port, and TLS terminates outside.
|
|
296
364
|
- A reverse proxy (Caddy, nginx) if you want a real hostname. Terminate TLS
|
|
297
|
-
there
|
|
298
|
-
|
|
299
|
-
|
|
365
|
+
there, preserve the external `Host` (or pass `X-Forwarded-Host`), and pass
|
|
366
|
+
`X-Forwarded-For`; Pier uses the external host for write-origin checks and
|
|
367
|
+
counts login failures per forwarded client. Its session cookie is marked
|
|
368
|
+
`Secure` when the proxy reports `X-Forwarded-Proto: https`.
|
|
300
369
|
|
|
301
370
|
## Backups
|
|
302
371
|
|
|
303
|
-
|
|
304
|
-
session map, workbench state, settings, the password hash
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
372
|
+
Three paths hold everything: `~/.pier/db/pier.db` (tasks, channels, the chat →
|
|
373
|
+
session map, workbench state, settings, the password hash, and the sealed
|
|
374
|
+
provider credentials and channel tokens), `~/.pier/master.key` (the key that
|
|
375
|
+
seals them — without it the database's sealed values are unreadable), and
|
|
376
|
+
`~/.pier/boards/`. `pier.db.release.bak` is the latest automatic pre-update copy.
|
|
377
|
+
For off-machine backups, use `sqlite3 ... "VACUUM INTO '…'"` rather than `cp`,
|
|
378
|
+
which under WAL can miss the most recent commits. Pi's own session history lives
|
|
379
|
+
under `~/.pier/pi` (Pier sets `PI_CODING_AGENT_DIR` there unless the environment
|
|
380
|
+
already names another directory).
|
|
381
|
+
|
|
382
|
+
Inbound chat attachments (photos, uploads from any surface) accumulate under
|
|
383
|
+
`~/.pier/inbox/<channel>/` and are never deleted by Pier — a transcript may
|
|
384
|
+
reference them indefinitely. Prune old files by hand (or a cron) when disk
|
|
385
|
+
matters; a pruned file degrades to a broken attachment link, nothing else.
|
package/package.json
CHANGED
|
@@ -38,18 +38,23 @@ Those four fields are the whole manifest.
|
|
|
38
38
|
|
|
39
39
|
`"public": true` serves the board at `/p/<slug>/` **with no password**. Set it
|
|
40
40
|
only if the user asked for a public or shareable board *in this request*;
|
|
41
|
-
otherwise leave it `false
|
|
42
|
-
|
|
43
|
-
anything the user has not seen.
|
|
41
|
+
otherwise leave it `false` and say the board is private. Never publish personal
|
|
42
|
+
data or anything the user has not seen.
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
Asked to make an existing board public? Flip `"public"` to `true` in
|
|
45
|
+
`board.json` and reply with the `/p/<slug>/` link — that is the whole answer.
|
|
46
|
+
No verification step, no narrating the edit, no restating what the page holds.
|
|
47
|
+
|
|
48
|
+
The message announcing the board carries **one bare URL** — paste the address
|
|
49
|
+
itself, never `[title](url)`: link labels get mangled or truncated on some chat
|
|
50
|
+
surfaces, and the title is already on the page. No filesystem paths either —
|
|
51
|
+
`…/boards/<slug>/board.json` means nothing to the reader.
|
|
47
52
|
`<pier>/AGENTS.md` gives you the address, so there is nothing to look up:
|
|
48
53
|
|
|
49
54
|
| The user asked for | Send |
|
|
50
55
|
| --- | --- |
|
|
51
|
-
| a board, nothing about sharing | `
|
|
52
|
-
| a **public** board | `
|
|
56
|
+
| a board, nothing about sharing | `https://pier.example.com/boards/weekly-digest/` — behind the Pier password; Console → Boards makes it public |
|
|
57
|
+
| a **public** board | `https://pier.example.com/p/weekly-digest/` — no password |
|
|
53
58
|
|
|
54
59
|
Never both: the pair invites pasting the password-free URL of a board that was
|
|
55
60
|
never meant to leave the workspace, and `/p/<slug>/` 404s unless the manifest
|
|
@@ -180,6 +185,10 @@ what makes it read as signal.
|
|
|
180
185
|
|
|
181
186
|
## Rules
|
|
182
187
|
|
|
188
|
+
- **No secrets, ever.** Tokens, API keys, credentials, internal hostnames and
|
|
189
|
+
private paths never go into a board — not in the page, not in a `<details>`
|
|
190
|
+
fold, not in a code sample. A private board is one Console toggle from
|
|
191
|
+
public, so write every page as if it already were.
|
|
183
192
|
- **Static and self-contained.** Everything the page needs lives under `site/`
|
|
184
193
|
with relative paths. No CDN, no external fonts, no analytics, no `fetch()` —
|
|
185
194
|
a published board is served under a CSP that blocks all of it, so an external
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pier-help
|
|
3
|
+
description: How Pier itself works — durable sessions and what survives a restart, how messages and files reach you from Slack and Telegram, in-chat commands (/stop, /settings, /bind), interrupting a running turn, and what only the operator's Console can change. Read before explaining Pier's behavior or advising a user on how to use it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How Pier works
|
|
7
|
+
|
|
8
|
+
Pier is the workspace this session runs in: agent sessions behind chat
|
|
9
|
+
surfaces — a web workbench and IM channels (Slack and Telegram today) — plus
|
|
10
|
+
scheduled tasks, subagents and boards. Answer questions about it from the
|
|
11
|
+
facts below. If the answer is not here, say you do not know how this instance
|
|
12
|
+
is configured rather than guessing: the Console (Pier's admin web UI) is the
|
|
13
|
+
operator's source of truth.
|
|
14
|
+
|
|
15
|
+
## Sessions and persistence
|
|
16
|
+
|
|
17
|
+
- One durable session per conversation: a web chat, a Slack thread, a
|
|
18
|
+
Telegram chat or topic. The mapping survives restarts — the next message
|
|
19
|
+
lands in the same transcript with its context intact.
|
|
20
|
+
- Idle sessions leave memory but keep their transcript; they resume
|
|
21
|
+
transparently on the next message. Never promise that a restart or a pause
|
|
22
|
+
wipes context.
|
|
23
|
+
- A fresh start is explicit: "New session" in the chat settings panel or the
|
|
24
|
+
web UI. The old transcript remains readable from the web workbench.
|
|
25
|
+
- The web workbench can also rewind to an earlier user turn and re-prompt;
|
|
26
|
+
IM surfaces cannot.
|
|
27
|
+
- A long session does not hit a wall: when the context fills, Pi compacts it
|
|
28
|
+
automatically — older turns become a summary. The transcript on disk keeps
|
|
29
|
+
everything, but detail can leave *your* context, so a very old turn is worth
|
|
30
|
+
re-reading rather than recalling. The web session header shows context used
|
|
31
|
+
and how much is left.
|
|
32
|
+
|
|
33
|
+
## Files and images the user sends
|
|
34
|
+
|
|
35
|
+
- A photo or file sent on any surface (web paste, Telegram photo/document,
|
|
36
|
+
Slack upload) is saved to `$PIER_HOME/inbox/` and reaches you as a trailing
|
|
37
|
+
`[name](file:///…)` line on the message — a path, not the content.
|
|
38
|
+
- Read it with the read tool only when it matters to the task: every read
|
|
39
|
+
puts the content in your context for good. An image you never read costs
|
|
40
|
+
nothing.
|
|
41
|
+
- The file stays on disk after the conversation moves on; link it back
|
|
42
|
+
(`[name](file:///…)`) whenever the user asks for it again.
|
|
43
|
+
|
|
44
|
+
## Messages while you are working
|
|
45
|
+
|
|
46
|
+
- On the web, a message sent mid-turn queues as a follow-up and lands after
|
|
47
|
+
the turn; a leading `!` interrupts instead — `!wrong file, stop` is injected
|
|
48
|
+
into the running turn as a steer.
|
|
49
|
+
- From an IM chat, every mid-turn message steers the running turn directly —
|
|
50
|
+
no `!` needed, and a leading `!` is just content.
|
|
51
|
+
- `/stop` aborts the current turn outright.
|
|
52
|
+
|
|
53
|
+
## In-chat commands and the settings panel
|
|
54
|
+
|
|
55
|
+
- `/settings` — or an addressed message with no text at all (a bare mention,
|
|
56
|
+
an empty DM) — opens a panel: model, reasoning level, new session
|
|
57
|
+
(optionally in a chosen directory), stop. Slack also accepts the bare words
|
|
58
|
+
`stop`, `settings`, `bind <code>`.
|
|
59
|
+
- Panel taps never reach you. The next-step buttons under your own replies
|
|
60
|
+
do — a click arrives as an ordinary user message with that label.
|
|
61
|
+
|
|
62
|
+
## What a turn looks like from outside
|
|
63
|
+
|
|
64
|
+
- Telegram and Slack put a 👀 on the message that started a turn and take it
|
|
65
|
+
off when the turn settles; a restart and a periodic sweep clear stragglers.
|
|
66
|
+
A 👀 that never clears means the turn died, not that you are still thinking.
|
|
67
|
+
- Every finished reply carries its cost: elapsed time and the context size at
|
|
68
|
+
completion (`1m14s · 32K tok`) — a running total, not this turn's spend. IM
|
|
69
|
+
shows it as a footer line, the web on hover.
|
|
70
|
+
- A reply past the platform's message cap is split across several messages
|
|
71
|
+
(Telegram ~3.8k chars); the footer and the next-step buttons ride the last
|
|
72
|
+
one.
|
|
73
|
+
|
|
74
|
+
## Who may talk (groups and binding)
|
|
75
|
+
|
|
76
|
+
- Group messages pass a per-chat gate the operator sets: it can require a
|
|
77
|
+
mention, require the sender to be bound, or both. Dropped messages are
|
|
78
|
+
logged, never answered.
|
|
79
|
+
- Binding: the operator issues a code in the Console; the user DMs the bot
|
|
80
|
+
`/bind <code>` (Slack: `bind <code>`). Codes expire after ~10 minutes.
|
|
81
|
+
- An unbound DM sender is told how to bind at most once per 10 minutes;
|
|
82
|
+
their other messages are dropped. "The bot ignores my DMs" usually means
|
|
83
|
+
not bound.
|
|
84
|
+
|
|
85
|
+
## Service restart, reload and update
|
|
86
|
+
|
|
87
|
+
- `pier restart` is the graceful systemd path: it refuses new work, waits up to
|
|
88
|
+
five minutes for active turns and Task runs, then restarts. If the deadline
|
|
89
|
+
aborts an IM turn, the next process tells that conversation.
|
|
90
|
+
- `pier reload` stays in-process: channel adapters re-read configuration and
|
|
91
|
+
idle, unwatched sessions reopen with current agent files on their next
|
|
92
|
+
message. Streaming or watched sessions are not interrupted.
|
|
93
|
+
- `pier update` is deliberately different: the separate updater hard-stops the
|
|
94
|
+
service, backs up the database, replaces the package, and starts it again.
|
|
95
|
+
It can interrupt active work. All three are operator shell commands for an
|
|
96
|
+
installed Linux systemd service, not tools available to the agent.
|
|
97
|
+
|
|
98
|
+
## Only the Console can change
|
|
99
|
+
|
|
100
|
+
Channel tokens and connections, per-chat gate policies, bind codes, provider
|
|
101
|
+
logins and credentials, the public address, security unlock. You have no tool
|
|
102
|
+
for any of these: point the user at the Console instead of improvising.
|
|
103
|
+
|
|
104
|
+
## The rest of the surface
|
|
105
|
+
|
|
106
|
+
- Chat conventions — next-step buttons, `file://` attachments, staying
|
|
107
|
+
silent, `[name<id> time]` sender headers — are in `<pier>/AGENTS.md`,
|
|
108
|
+
already in your context.
|
|
109
|
+
- Delegating and scheduling work: the pier-tasks skill. Reading and posting
|
|
110
|
+
Slack: pier-slack. Presenting a report as a page: pier-boards.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pier-slack
|
|
3
|
-
description: Read and write Slack through Pier's slack tool — a channel's history for a time range, one thread, posting into a thread, and the Slack-specific syntax for mentions and links. Read before answering questions about Slack conversations or posting anything to a workspace.
|
|
3
|
+
description: Read and write Slack through Pier's slack tool — a channel's history for a time range, one thread, posting into a thread, deleting a message Pier posted, and the Slack-specific syntax for mentions and links. Read before answering questions about Slack conversations or posting anything to a workspace.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Reading and writing Slack
|
|
@@ -31,8 +31,9 @@ wanted. `not_in_channel` means someone must run `/invite @Pier`.
|
|
|
31
31
|
You already have them:
|
|
32
32
|
|
|
33
33
|
- **The person talking to you** — a message may start with `[name<id> time]`,
|
|
34
|
-
added by Pier, not typed by them. It appears only
|
|
35
|
-
|
|
34
|
+
added by Pier, not typed by them. It appears only on a change — new speaker,
|
|
35
|
+
a time gap, a new day — so the last one you saw still applies (a gap alone
|
|
36
|
+
shows as time only, like `[14:23]`).
|
|
36
37
|
- **Anyone in a transcript** — every line carries `name[id]`.
|
|
37
38
|
- **This channel and thread** — from `context`.
|
|
38
39
|
|
|
@@ -96,6 +97,22 @@ Going top-level takes the explicit `"none"`: a channel's main flow is wider
|
|
|
96
97
|
than a thread. A `thread_ts` is never inherited across a change of `channel`.
|
|
97
98
|
The response carries `ts` and `threadTs` for replying under what you posted.
|
|
98
99
|
|
|
100
|
+
## Deleting
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{"operation":"delete","channel":"#ops","ts":"1717243800.000100"}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Slack only lets Pier delete what **its own bot** posted — a person's message
|
|
107
|
+
answers `cant_delete_message`, and the answer is to ask them, not to retry.
|
|
108
|
+
|
|
109
|
+
- `ts` is always explicit. There is no "delete the last one" and no default
|
|
110
|
+
from the thread you are in: the wrong `ts` cannot be undone.
|
|
111
|
+
- A `ts` means nothing outside the conversation it came from — pass the
|
|
112
|
+
`channel` it was read in.
|
|
113
|
+
- Deleting a thread parent does not remove its replies. Say what you removed;
|
|
114
|
+
a message vanishing with no word looks like a Slack bug to everyone watching.
|
|
115
|
+
|
|
99
116
|
## Message syntax
|
|
100
117
|
|
|
101
118
|
`text` is **standard markdown** and Slack renders it natively: `**bold**`,
|
|
@@ -70,18 +70,25 @@ history) — every prompt must be self-contained: paths, acceptance criteria,
|
|
|
70
70
|
expected output format. Branching and retries are your own logic between
|
|
71
71
|
turns.
|
|
72
72
|
|
|
73
|
-
## Choosing a model
|
|
74
|
-
|
|
75
|
-
Omit `launch.model` to inherit your own model — the right default.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- `
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
## Choosing a model and thinking level
|
|
74
|
+
|
|
75
|
+
Omit `launch.model` to inherit your own model — the right default. When the
|
|
76
|
+
task profile clearly differs, adjust in this order:
|
|
77
|
+
|
|
78
|
+
- **Harder reasoning → raise `launch.thinking` first**, on the inherited
|
|
79
|
+
model (`off` … `minimal` `low` `medium` `high` `xhigh` `max`). It needs no
|
|
80
|
+
model name and is usually cheaper than switching models.
|
|
81
|
+
- **Cheap bulk work** (listings, extraction, simple checks) → the same
|
|
82
|
+
provider's smallest current model, with thinking low or off.
|
|
83
|
+
- **Cross-vendor second opinion** → another vendor's flagship.
|
|
84
|
+
|
|
85
|
+
Never write a model id from memory — catalogs move under you.
|
|
86
|
+
`{"operation":"models"}` is the authority: it returns the deployment's menu —
|
|
87
|
+
operator-pinned models when set (`source:"menu"`), each with an intent note
|
|
88
|
+
and often a usual `thinking` level, the curated live catalog otherwise
|
|
89
|
+
(`source:"catalog"`). Prefer a pinned entry whose note matches the task and
|
|
90
|
+
start from its thinking level. A wrong id still fails with the available
|
|
91
|
+
list in the error, so a stale guess costs one call.
|
|
85
92
|
|
|
86
93
|
## Mid-run control
|
|
87
94
|
|