@timqi/pier 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +58 -125
  2. package/dist/agent/config.js +6 -15
  3. package/dist/agent/credentials.js +11 -23
  4. package/dist/agent/events.js +42 -64
  5. package/dist/agent/listing.js +39 -92
  6. package/dist/agent/pi.js +103 -252
  7. package/dist/boards/boards.js +19 -29
  8. package/dist/channels/attach.js +14 -42
  9. package/dist/channels/chains.js +33 -37
  10. package/dist/channels/chunk.js +8 -28
  11. package/dist/channels/commands.js +3 -14
  12. package/dist/channels/config.js +33 -52
  13. package/dist/channels/control.js +4 -13
  14. package/dist/channels/conversations.js +8 -25
  15. package/dist/channels/dedup.js +8 -17
  16. package/dist/channels/gatekeeper.js +13 -23
  17. package/dist/channels/lark-api.js +23 -63
  18. package/dist/channels/lark-outbound.js +12 -44
  19. package/dist/channels/lark-panel.js +7 -23
  20. package/dist/channels/lark-render.js +18 -62
  21. package/dist/channels/lark.js +52 -141
  22. package/dist/channels/lines.js +13 -15
  23. package/dist/channels/panel.js +16 -36
  24. package/dist/channels/receipts.js +29 -52
  25. package/dist/channels/routes.js +3 -9
  26. package/dist/channels/runtime.js +12 -23
  27. package/dist/channels/slack-api.js +34 -86
  28. package/dist/channels/slack-directory.js +7 -23
  29. package/dist/channels/slack-outbound.js +12 -56
  30. package/dist/channels/slack-panel.js +4 -13
  31. package/dist/channels/slack-render.js +23 -91
  32. package/dist/channels/slack-tool.js +48 -171
  33. package/dist/channels/slack.js +73 -239
  34. package/dist/channels/telegram-api.js +8 -20
  35. package/dist/channels/telegram-panel.js +5 -21
  36. package/dist/channels/telegram-render.js +13 -40
  37. package/dist/channels/telegram.js +54 -146
  38. package/dist/channels/types.js +5 -16
  39. package/dist/cli.js +17 -41
  40. package/dist/config-sync.js +87 -4
  41. package/dist/core/hub.js +7 -20
  42. package/dist/core/identity.js +20 -59
  43. package/dist/core/inbound-file.js +15 -49
  44. package/dist/core/inbox.js +12 -34
  45. package/dist/core/queue.js +3 -5
  46. package/dist/core/reply.js +41 -142
  47. package/dist/core/router.js +209 -264
  48. package/dist/core/types.js +4 -0
  49. package/dist/db.js +88 -272
  50. package/dist/drain.js +57 -50
  51. package/dist/extensions/index.js +3 -11
  52. package/dist/extensions/web/anthropic.js +3 -9
  53. package/dist/extensions/web/artifacts.js +2 -5
  54. package/dist/extensions/web/content.js +4 -12
  55. package/dist/extensions/web/http.js +2 -6
  56. package/dist/extensions/web/language.js +8 -18
  57. package/dist/extensions/web/openai.js +1 -1
  58. package/dist/extensions/web/provider.js +5 -18
  59. package/dist/extensions/web/tools.js +19 -63
  60. package/dist/lock.js +98 -0
  61. package/dist/log.js +9 -26
  62. package/dist/main.js +84 -183
  63. package/dist/paths.js +10 -26
  64. package/dist/secrets.js +18 -45
  65. package/dist/service.js +31 -73
  66. package/dist/settings.js +19 -63
  67. package/dist/tasks/agent.js +24 -45
  68. package/dist/tasks/callbacks.js +8 -16
  69. package/dist/tasks/command.js +2 -6
  70. package/dist/tasks/definitions.js +39 -62
  71. package/dist/tasks/execution.js +41 -39
  72. package/dist/tasks/groups.js +8 -11
  73. package/dist/tasks/messages.js +88 -155
  74. package/dist/tasks/outbox.js +33 -54
  75. package/dist/tasks/routes.js +4 -7
  76. package/dist/tasks/runs.js +4 -9
  77. package/dist/tasks/service.js +29 -42
  78. package/dist/tasks/store.js +36 -27
  79. package/dist/tasks/tool.js +59 -60
  80. package/dist/tools-task.js +20 -60
  81. package/dist/tools.js +98 -325
  82. package/dist/update.js +20 -43
  83. package/dist/web/auth.js +118 -179
  84. package/dist/web/config-sync.js +2 -2
  85. package/dist/web/config.js +3 -7
  86. package/dist/web/explorer.js +10 -21
  87. package/dist/web/fs.js +20 -42
  88. package/dist/web/instance.js +35 -82
  89. package/dist/web/providers.js +5 -11
  90. package/dist/web/public/assets/{activity-Bl3vZukb.js → activity-DOr8dWeX.js} +1 -1
  91. package/dist/web/public/assets/activity-DOr8dWeX.js.br +0 -0
  92. package/dist/web/public/assets/activity-DOr8dWeX.js.gz +0 -0
  93. package/dist/web/public/assets/{boards-DYuf4Mlj.js → boards-CneyR23E.js} +1 -1
  94. package/dist/web/public/assets/boards-CneyR23E.js.br +0 -0
  95. package/dist/web/public/assets/boards-CneyR23E.js.gz +0 -0
  96. package/dist/web/public/assets/explorer-D0srXT1c.js +4 -0
  97. package/dist/web/public/assets/explorer-D0srXT1c.js.br +0 -0
  98. package/dist/web/public/assets/explorer-D0srXT1c.js.gz +0 -0
  99. package/dist/web/public/assets/index-DbFu15NN.js +85 -0
  100. package/dist/web/public/assets/index-DbFu15NN.js.br +0 -0
  101. package/dist/web/public/assets/index-DbFu15NN.js.gz +0 -0
  102. package/dist/web/public/assets/index-eqQLVS8Q.css +2 -0
  103. package/dist/web/public/assets/index-eqQLVS8Q.css.br +0 -0
  104. package/dist/web/public/assets/index-eqQLVS8Q.css.gz +0 -0
  105. package/dist/web/public/assets/{runs-BLJu7EXN.js → runs-Cv0A-e08.js} +1 -1
  106. package/dist/web/public/assets/runs-Cv0A-e08.js.br +0 -0
  107. package/dist/web/public/assets/runs-Cv0A-e08.js.gz +0 -0
  108. package/dist/web/public/assets/{settings-BrdVh-Zi.js → settings-VmCjhGBd.js} +1 -1
  109. package/dist/web/public/assets/settings-VmCjhGBd.js.br +0 -0
  110. package/dist/web/public/assets/settings-VmCjhGBd.js.gz +0 -0
  111. package/dist/web/public/assets/{task-runs-CeQS1rxa.js → task-runs-CqpTV644.js} +1 -1
  112. package/dist/web/public/assets/task-runs-CqpTV644.js.br +0 -0
  113. package/dist/web/public/assets/task-runs-CqpTV644.js.gz +0 -0
  114. package/dist/web/public/assets/tasks-BffPVgXg.js +4 -0
  115. package/dist/web/public/assets/tasks-BffPVgXg.js.br +0 -0
  116. package/dist/web/public/assets/tasks-BffPVgXg.js.gz +0 -0
  117. package/dist/web/public/index.html +30 -16
  118. package/dist/web/public/index.html.br +0 -0
  119. package/dist/web/public/index.html.gz +0 -0
  120. package/dist/web/public/sw.js +14 -2
  121. package/dist/web/public/sw.js.br +0 -0
  122. package/dist/web/public/sw.js.gz +0 -0
  123. package/dist/web/push.js +55 -77
  124. package/dist/web/route.js +3 -7
  125. package/dist/web/server.js +109 -190
  126. package/dist/web/session-state.js +13 -53
  127. package/dist/web/types.js +2 -4
  128. package/dist/web/webpush.js +10 -25
  129. package/docs/deploy.md +115 -330
  130. package/package.json +1 -1
  131. package/skills/pier-boards/SKILL.md +81 -160
  132. package/skills/pier-help/SKILL.md +23 -20
  133. package/skills/pier-slack/SKILL.md +2 -2
  134. package/skills/pier-tasks/SKILL.md +23 -15
  135. package/dist/config-sync-fetch.js +0 -84
  136. package/dist/limits.js +0 -14
  137. package/dist/web/public/assets/activity-Bl3vZukb.js.br +0 -0
  138. package/dist/web/public/assets/activity-Bl3vZukb.js.gz +0 -0
  139. package/dist/web/public/assets/boards-DYuf4Mlj.js.br +0 -0
  140. package/dist/web/public/assets/boards-DYuf4Mlj.js.gz +0 -0
  141. package/dist/web/public/assets/explorer-qJH_9nTE.js +0 -4
  142. package/dist/web/public/assets/explorer-qJH_9nTE.js.br +0 -0
  143. package/dist/web/public/assets/explorer-qJH_9nTE.js.gz +0 -0
  144. package/dist/web/public/assets/index-Dqdb-Eqt.js +0 -85
  145. package/dist/web/public/assets/index-Dqdb-Eqt.js.br +0 -0
  146. package/dist/web/public/assets/index-Dqdb-Eqt.js.gz +0 -0
  147. package/dist/web/public/assets/index-DzmMzvi_.css +0 -2
  148. package/dist/web/public/assets/index-DzmMzvi_.css.br +0 -0
  149. package/dist/web/public/assets/index-DzmMzvi_.css.gz +0 -0
  150. package/dist/web/public/assets/runs-BLJu7EXN.js.br +0 -0
  151. package/dist/web/public/assets/runs-BLJu7EXN.js.gz +0 -0
  152. package/dist/web/public/assets/settings-BrdVh-Zi.js.br +0 -0
  153. package/dist/web/public/assets/settings-BrdVh-Zi.js.gz +0 -0
  154. package/dist/web/public/assets/task-runs-CeQS1rxa.js.br +0 -0
  155. package/dist/web/public/assets/task-runs-CeQS1rxa.js.gz +0 -0
  156. package/dist/web/public/assets/tasks-bcb3fYdK.js +0 -4
  157. package/dist/web/public/assets/tasks-bcb3fYdK.js.br +0 -0
  158. package/dist/web/public/assets/tasks-bcb3fYdK.js.gz +0 -0
@@ -5,10 +5,9 @@ description: Publish a Board — a folder of static HTML Pier serves at a stable
5
5
 
6
6
  # Building a Pier board
7
7
 
8
- A **board** is a directory in the boards folder `<pier>/AGENTS.md` names under
9
- "This Pier instance" — use that path verbatim; `~/.pier` is only the default.
10
- Pier serves `<board>/site/` and nothing else. Boards outlive sessions: any
11
- session may read or rewrite any board, and closing this one changes nothing.
8
+ A **board** lives in the exact boards folder named under "This Pier instance"
9
+ in `<pier>/AGENTS.md`; do not assume `~/.pier`. Only `<board>/site/` is served.
10
+ Boards survive sessions; any session may read or update them.
12
11
 
13
12
  ## Create one
14
13
 
@@ -27,13 +26,9 @@ session may read or rewrite any board, and closing this one changes nothing.
27
26
  }
28
27
  ```
29
28
 
30
- Those four fields are the whole manifest (publishing adds a fifth — below).
31
-
32
- - `slug`: `[a-z0-9][a-z0-9-]{0,63}`, and it is the URL — short and stable. Do
33
- not add random characters of your own; publishing adds them (see below).
34
- - `description` is the Console list entry: write it for someone who has
35
- forgotten this conversation.
36
- - `sessions`: append your own id, never replace — other ids are provenance too.
29
+ - `slug`: `[a-z0-9][a-z0-9-]{0,63}`; keep it short and stable, without random suffixes.
30
+ - `description`: Console list text, understandable without this conversation.
31
+ - `sessions`: append your id; preserve others as provenance.
37
32
 
38
33
  ## Publish, then hand over the link
39
34
 
@@ -42,40 +37,25 @@ asked for a public or shareable board *in this request*; otherwise leave it
42
37
  `false` and say the board is private. Never publish personal data or anything
43
38
  the user has not seen.
44
39
 
45
- The published address is `/p/<slug>-<token>/`, not `/p/<slug>/`, so a public
46
- board's URL cannot be guessed from its name. `token` is a fifth manifest field
47
- you write next to `"public": true` — eight hex characters from
48
- `openssl rand -hex 4`, never invented in your head, never reused between
49
- boards. Leave it out and Pier mints one on the first request, but then the link
50
- is only visible in the Console, so write it yourself and you can hand it over
51
- in the same message.
52
-
53
- Asked to make an existing board public? Set `"public": true` and a fresh
54
- `token` in `board.json`, then reply with the `/p/<slug>-<token>/` link — that is
55
- the whole answer. Already has a token? Keep it: the link may be out there. No
56
- verification step, no narrating the edit, no restating what the page holds.
57
-
58
- The message announcing the board carries **one bare URL** — paste the address
59
- itself, never `[title](url)`: link labels get mangled or truncated on some chat
60
- surfaces, and the title is already on the page. No filesystem paths either —
61
- `…/boards/<slug>/board.json` means nothing to the reader.
62
- `<pier>/AGENTS.md` gives you the address, so there is nothing to look up:
63
-
64
- | The user asked for | Send |
40
+ For publishing, preserve the manifest's `token` or add eight hex characters
41
+ from `openssl rand -hex 4`; never invent or reuse another board's token.
42
+
43
+ Use the instance address from `<pier>/AGENTS.md` plus:
44
+
45
+ | Visibility | Path |
65
46
  | --- | --- |
66
- | a board, nothing about sharing | `https://pier.example.com/boards/weekly-digest/` — behind the Pier password; Console → Boards makes it public |
67
- | a **public** board | `https://pier.example.com/p/weekly-digest-3f9ac128/` — no password; the suffix is the manifest's `token`, copied verbatim |
47
+ | Private (default) | `/boards/<slug>/` — password required; Console → Boards can publish |
48
+ | Public | `/p/<slug>-<token>/` — copy the token verbatim; 404 unless `public: true` |
68
49
 
69
- Never both: the pair invites pasting the password-free URL of a board that was
70
- never meant to leave the workspace, and `/p/<slug>-<token>/` 404s unless the manifest
71
- says `"public": true`. No address configured? Give the path, say Console →
72
- Settings turns it into a link, and never guess a host.
50
+ Return **one bare URL**, never both, Markdown link labels or filesystem paths.
51
+ If no address is configured, give the path and point to Console → Settings;
52
+ never guess a host. For a publish-only request, set `public: true` and a token
53
+ if missing, then return only the public URL; no page verification or narration.
73
54
 
74
55
  ## Writing the page
75
56
 
76
- A board is a **presentation**, not a text file: someone opens it to get an
77
- answer fast. Link the shipped stylesheet and write plain semantic HTML — no
78
- build, no npm, no framework:
57
+ Let the question and evidence determine layout; no sections are required.
58
+ Use semantic HTML without a framework. Adapt this shell's language and content:
79
59
 
80
60
  ```html
81
61
  <!doctype html>
@@ -87,143 +67,84 @@ build, no npm, no framework:
87
67
  <link rel="stylesheet" href="/p/_assets/pier.css">
88
68
  </head>
89
69
  <body>
90
- <div class="hero">
91
- <h1>Weekly digest — infra</h1>
92
- <p class="lede">Throughput is up, but two migrations need a decision before Friday.</p>
93
- <p class="muted">18 Feb 2026 · covers 12 repos</p>
94
- </div>
95
-
96
- <div class="grid">
97
- <div class="card"><span class="kpi good">12</span> PRs merged <span class="muted">+3 vs last week</span></div>
98
- <div class="card"><span class="kpi warn">2</span> awaiting decision</div>
99
- <div class="card"><span class="kpi">98.9%</span> uptime <span class="muted">30 d</span></div>
100
- </div>
101
-
102
- <div class="callout warn">
103
- <strong>Needs you:</strong> the payments migration blocks two teams — approve or defer by Friday.
104
- </div>
105
-
106
- <h2>Payments is the only degraded service</h2>
107
- <table>
108
- <thead><tr><th>Service</th><th>Status</th><th class="num">p95</th><th>Error budget</th></tr></thead>
109
- <tbody>
110
- <tr><td><strong>payments</strong></td><td><span class="tag bad">degraded</span></td><td class="num">910 ms</td>
111
- <td><span class="bar bad" style="--v:18%"></span></td></tr>
112
- <tr><td>api</td><td><span class="tag good">healthy</span></td><td class="num">142 ms</td>
113
- <td><span class="bar good" style="--v:82%"></span></td></tr>
114
- </tbody>
115
- </table>
116
-
117
- <details><summary>All 12 merged PRs</summary>
118
- <table><thead><tr><th>PR</th><th>Author</th><th>Merged</th></tr></thead>
119
- <tbody><tr><td>#412</td><td>ana</td><td>Mon</td></tr></tbody></table>
120
- </details>
121
-
122
- <footer>Written by Pier · data as of 18 Feb 09:00 · ask for an update to refresh</footer>
70
+ <h1>Weekly digest — infra</h1>
71
+ <p class="lede">The main finding, grounded in the available evidence.</p>
72
+ <footer>Data as of … · sources … · how to refresh …</footer>
123
73
  </body>
124
74
  </html>
125
75
  ```
126
76
 
127
- What makes it read as designed rather than generated:
128
-
129
- - **The lede is the verdict, not the topic.** "Two migrations need a decision by
130
- Friday" answers; "This digest covers infra activity" restates the prompt. A
131
- reader who stops after the hero must still leave with the point.
132
- - **Headings are findings.** "Payments is the only degraded service", not
133
- "Services". A heading that could top any report — Overview, Summary, Details —
134
- says nothing about this one.
135
- - **Every number carries its unit and its baseline**: "142 ms p95, was 120",
136
- "12 of 40". A bare number is decoration; so is fake precision (98.8724% →
137
- 98.9%).
138
- - **End at the last useful block.** No closing summary, no filler section. The
139
- footer holds provenance: data as-of, source, how to refresh.
77
+ Lead with the verdict; headings state findings ("Payments alone is degraded").
78
+ Numbers carry units and baselines ("142 ms p95, was 120", "12 of 40"), without
79
+ false precision. End without filler or a closing summary; put the data
80
+ timestamp, sources and refresh instructions in the footer.
140
81
 
141
82
  ## What `pier.css` gives you
142
83
 
143
- Headings, paragraphs, lists, tables, `pre`/`code`, `blockquote`, `details` and
144
- `footer` are styled with **no classes at all**, dark mode included. The page
145
- sizes itself: a wide desktop canvas (62rem, 76rem on a very large screen) that
146
- reflows to one column on a phone, prose held to a readable measure while tables,
147
- `.grid`, `.split` and `.hero` use the full width — don't add a `max-width` of
148
- your own. Zebra striping, tables that scroll inside themselves, and `<details>`
149
- that print open are free. On top of that:
84
+ Classless defaults include readable prose, responsive widths, dark mode,
85
+ scrolling zebra tables and print styles. Override widths only as needed.
150
86
 
151
87
  | Class | Use it for |
152
88
  | --- | --- |
153
- | `.lede` | the one-sentence answer under the title |
154
- | `.hero` | the opening block: title + lede + date/scope, on a tinted panel |
155
- | `.grid` + `.card` | a KPI row (auto-fits to one column on a phone) |
156
- | `.kpi` | the headline number inside a card |
157
- | `.card` + `.good/.warn/.bad/.info` | a status card: accent edge and tinted fill |
158
- | `.callout` (`.good` `.warn` `.bad`) | the takeaway or the ask |
159
- | `.tag` (`.good` `.warn` `.bad` `.info`) | status pills in tables and lists |
160
- | `.good` `.warn` `.bad` | colour on a number or a word |
161
- | `.num` | right-aligned, tabular numeric table cells |
162
- | `.bar` (`.good` `.warn` `.bad`) | share-of-total inside a table: `style="--v:62%"` |
163
- | `.split` | two columns that stack on a phone: before/after, text + aside |
164
- | `.muted` | secondary text: dates, deltas, units, scope |
165
-
166
- Need something it lacks? A `<style>` block or your own CSS file inside `site/`
167
- is normal, and so is a custom colour or a hand-written layout when the content
168
- calls for one.
89
+ | `.lede` / `.hero` | opening answer / tinted opening panel |
90
+ | `.grid` + `.card` + `.kpi` | responsive cards with headline numbers |
91
+ | `.callout` / `.tag` | takeaway or ask / status pill |
92
+ | `.num` | right-aligned tabular numbers |
93
+ | `.bar` | proportion: `style="--v:62%"` |
94
+ | `.split` | two columns, stacking on phones |
95
+ | `.muted` | dates, deltas, units, scope |
96
+
97
+ Status modifiers: `.good` healthy/done, `.warn` attention/pending, `.bad`
98
+ broken/blocked; express status in words too. These colour text, cards,
99
+ callouts, tags and bars; cards and tags also accept `.info` for neutral emphasis.
100
+ All helpers are optional. Custom `<style>` or CSS under `site/` may change
101
+ layout and palette while preserving contrast and phone reflow.
169
102
 
170
103
  ## Pick the form from the content
171
104
 
172
- | What you have | How to present it |
105
+ | Content | Possible form |
173
106
  | --- | --- |
174
- | The single most important fact | `.hero` lede, or one `.kpi` on its own |
175
- | 2–4 headline metrics | `.grid` of `.card`s, each with `.kpi` + a `.muted` delta |
176
- | Something the reader must act on | `.callout warn` near the top, naming the deadline |
177
- | Items with a state | table with `.tag` pills, worst rows first |
178
- | Ranked or compared numbers | table, `.num` columns, `.bar` for share of total |
179
- | Progress toward a goal | `.bar` per row, or `.kpi` + "of 40 done" in `.muted` |
180
- | Two alternatives | `.split` with a `.card` each, verdict in the lede above |
181
- | A sequence of events | ordered list, date in `.muted` at the start of each item |
182
- | A trend over time | first/last + delta in words; inline SVG only if the shape *is* the news |
183
- | 200 rows | aggregate, show the 5–10 that matter, rest in `<details>` with the count in its summary |
184
- | 1–2 data points | a sentence or a `.callout` — a one-row table is a table costume |
185
- | Long raw output, logs, code | `<details>` at the bottom, or `<pre><code>` trimmed to the lines that matter |
186
- | Nothing to report | say so in the lede ("all 14 checks green") and stop — short is finished, not thin |
187
-
188
- Vary the forms: never two blocks of the same kind in a row, and prefer number →
189
- ask → proof → fold over six paragraphs. A status or decision board earns one
190
- screenful before the first `<details>`; only a handover or postmortem earns a
191
- long scroll. Colour is part of the message — green healthy/done, amber
192
- attention/pending, red broken/blocked, `.info` neutral emphasis — on the numbers
193
- and status cells that carry the point, never on ordinary prose; that contrast is
194
- what makes it read as signal.
107
+ | One finding or 1–2 values | sentence; if nothing changed, say so and stop |
108
+ | Headline metrics | KPI cards with deltas; progress includes the total |
109
+ | Required action | prominent ask with deadline |
110
+ | States, rankings or shared comparison criteria | table; worst states first |
111
+ | Alternatives needing separate explanations | parallel sections; recommendation first |
112
+ | Events / trends | dated list / endpoints and delta; chart when shape explains the finding |
113
+ | Large datasets or raw output | aggregate, show relevant rows, fold the rest with a count; trim code/logs |
114
+
115
+ Repeat forms for comparable items; vary them when information changes. Put
116
+ findings and actions first, then evidence; fold only supplementary detail.
195
117
 
196
118
  ## Rules
197
119
 
198
- - **No secrets, ever.** Tokens, API keys, credentials, internal hostnames and
199
- private paths never go into a board — not in the page, not in a `<details>`
200
- fold, not in a code sample. A private board is one Console toggle from
201
- public, so write every page as if it already were.
202
- - **Static and self-contained.** Everything the page needs lives under `site/`
203
- with relative paths. No CDN, no external fonts, no analytics, no `fetch()` —
204
- a published board is served under a CSP that blocks all of it, so an external
205
- reference is a broken page, not a slow one.
206
- - **Content, not an app.** Interaction is `<details>` and anchors; the page
207
- stays readable with JavaScript off.
208
- - **Show, don't narrate.** A sentence describing numbers should have been a KPI
209
- row or a table. Prose is for judgement — what it means, what to do.
210
- - **Structure before graphics.** A chart is inline SVG (no library) and only
211
- when the *shape* of the data is the message. A three-row table beats any
212
- picture of three numbers; no chart beats a decorative one.
213
- - **Only real data.** Every figure traces to something you saw this session, and
214
- a gap is shown as a gap ("no data since Tue", "~3 weeks", "n=3") — never
215
- smoothed into a clean number.
216
- - **Rewrite in place.** Updating a board means editing its files, not creating
217
- `weekly-digest-v2`. The URL is the point.
218
- - Top-down — what this is → the answer → detail → raw data in `<details>` —
219
- prose in the user's language, short headings, no emoji chrome.
120
+ - **No secrets:** no tokens, API keys, credentials, internal hostnames or
121
+ private paths in page content, including folds and code samples; private
122
+ boards can become public with one Console toggle.
123
+ - **Self-contained:** assets live under `site/` with relative paths, except
124
+ the shipped stylesheet; no CDN, external fonts, analytics or `fetch()`
125
+ (blocked by the public CSP).
126
+ - **Interaction:** prefer `<details>` and anchors; local JS may sort, filter or
127
+ inspect embedded data, without network calls or server runtime. With JS off,
128
+ keep the answer and evidence readable; hide or disable unavailable controls.
129
+ - **Charts:** use when data shape explains the finding; prefer inline SVG,
130
+ a small bundled library only for needed interaction, with text/table fallback.
131
+ - **Real data:** every figure traces to evidence seen this session; expose
132
+ gaps and uncertainty ("no data since Tue", "~3 weeks", "n=3").
133
+ - **Update in place:** edit the existing board; preserve its URL.
134
+ - Write in the user's language, with short headings and no emoji chrome.
135
+
136
+ ## Check the page
137
+
138
+ For new pages or layout/interaction changes, run the checks below; for content
139
+ edits, check the affected content and links. Report actual coverage or gaps.
140
+
141
+ - Phone/desktop, light/dark: readable contrast, no page overflow, tables scroll internally.
142
+ - Keyboard/touch: working links and controls, visible focus, usable targets, text status labels.
143
+ - Assets load without console errors; answer and evidence remain readable with JS off.
220
144
 
221
145
  ## If a board needs a build
222
146
 
223
- Pier ships no toolchain and the default is no build. If one is genuinely needed
224
- (a bundled charting library, a component layout), you own it: keep sources
225
- outside `site/` (e.g. `<board>/src/`), emit into `site/`, and write a
226
- `<board>/README.md` a future session can follow cold — install command, build
227
- command, output path, where the data came from. Never leave `site/`
228
- inconsistent with its sources; on an existing board look for that README first
229
- and rebuild, because hand-patching `site/` is lost on the next build.
147
+ Pier ships no toolchain. If a build is needed, keep sources outside `site/`,
148
+ emit into `site/`, and document install/build commands, output path and data
149
+ sources in `<board>/README.md`. Before editing an existing board, check for
150
+ that README; update sources and rebuild so `site/` stays consistent.
@@ -6,15 +6,15 @@ description: How Pier itself works — durable sessions, what survives a restart
6
6
  # How Pier works
7
7
 
8
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.
9
+ surfaces — a web workbench and IM channels (Slack, Telegram, Lark) — plus
10
+ scheduled tasks, subagents and boards. Answer from the facts below. If the
11
+ answer is not here, say you do not know how this instance is configured rather
12
+ than guessing: the Console (Pier's admin web UI) is the operator's source of
13
+ truth.
14
14
 
15
15
  ## Sessions and persistence
16
16
 
17
- - One durable session per conversation: a web chat, a Slack thread, a
17
+ - One durable session per conversation: a web chat, a Slack or Lark thread, a
18
18
  Telegram chat or topic. The mapping survives restarts — the next message
19
19
  lands in the same transcript with its context intact.
20
20
  - Idle sessions leave memory but keep their transcript; they resume
@@ -48,7 +48,8 @@ operator's source of truth.
48
48
  into the running turn as a steer.
49
49
  - From an IM chat, every mid-turn message steers the running turn directly —
50
50
  no `!` needed, and a leading `!` is just content.
51
- - `/stop` aborts the current turn outright.
51
+ - `/stop` from an IM chat aborts the current turn outright; the web has a Stop
52
+ button.
52
53
 
53
54
  ## In-chat commands and the settings panel
54
55
 
@@ -61,12 +62,13 @@ operator's source of truth.
61
62
 
62
63
  ## What a turn looks like from outside
63
64
 
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.
65
+ - IM channels put a 👀 (Lark: "OnIt") on the message that started a turn and
66
+ take it off when the turn settles; a restart and a periodic sweep clear stragglers.
66
67
  A 👀 that never clears means the turn died, not that you are still thinking.
67
68
  - Every finished reply carries its cost: elapsed time and the context size at
68
69
  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
+ shows it as a footer line; the web shows the duration in the reply's activity
71
+ headline and the context size in the session header.
70
72
  - A reply past the platform's message cap is split across several messages
71
73
  (Telegram ~3.8k chars); the footer and the next-step buttons ride the last
72
74
  one.
@@ -96,16 +98,17 @@ operator's source of truth.
96
98
 
97
99
  ## Service restart, reload and update
98
100
 
99
- - `pier restart` is the graceful systemd path: it refuses new work, waits up to
100
- five minutes for active turns and Task runs, then restarts. If the deadline
101
- aborts an IM turn, the next process tells that conversation.
102
- - `pier reload` stays in-process: channel adapters re-read configuration and
103
- idle, unwatched sessions reopen with current agent files on their next
104
- message. Streaming or watched sessions are not interrupted.
105
- - `pier update` is deliberately different: the separate updater hard-stops the
106
- service, backs up the database, replaces the package, and starts it again.
107
- It can interrupt active work. All three are operator shell commands for an
108
- installed Linux systemd service, not tools available to the agent.
101
+ - `pier restart`: refuses new work, waits up to five minutes for active turns
102
+ and Task runs, then restarts. If the deadline aborts an IM turn, the next
103
+ process tells that conversation.
104
+ - `pier reload`: channel adapters re-read configuration and idle, unwatched
105
+ sessions reopen with current agent files on their next message. Streaming or
106
+ watched sessions are not interrupted.
107
+ - `pier update`: a separate updater backs up the database and installs the new
108
+ package while Pier is still up, then hard-stops and starts the service. From
109
+ the shell it does not drain, so it can interrupt active work; the Console's
110
+ Update and auto-update drain first. All three are operator shell commands for an installed Linux systemd
111
+ service, not tools available to the agent.
109
112
 
110
113
  ## Only the Console can change
111
114
 
@@ -30,8 +30,8 @@ Four things markdown cannot express:
30
30
  thread. Asking a human to paste their own user ID is never acceptable.
31
31
  - Escape `&` `<` `>` when they are text, not markup: `&amp;` `&lt;` `&gt;`.
32
32
  - Emoji as `:white_check_mark:`, not the raw glyph.
33
- - ~11,000 chars per message: split longer content across replies in one thread
34
- rather than truncating.
33
+ - 11,000 chars per message; the tool refuses longer `text`. Split longer
34
+ content across replies in one thread rather than truncating.
35
35
 
36
36
  ## Targeting
37
37
 
@@ -25,6 +25,8 @@ status query. The receipt's `next` tells you where/how delivery happens:
25
25
  not for work you launch and forget. Receipt echoes `callbackMode`.
26
26
  - `callback:"none"`: no delivery; means you do not want the result, not pull later.
27
27
  - Single-run `callback_session_id`: deliver to another existing session.
28
+ Top-level sessions only, and never with `callback:"none"` or `tasks[]`;
29
+ inside a run it is refused, so your result always returns to you.
28
30
 
29
31
  Receipts include `runId`, `taskId`, state. Keep IDs: the callback also names
30
32
  `Run:`, and there is no lookup by task. `triggerSource` names the actual invoker
@@ -47,20 +49,21 @@ policy (`manual` means on demand).
47
49
  - Stored role: `run` with `task_id` and optional `input`;
48
50
  `session_mode:"fresh"` overrides a stored reuse policy. Archived tasks refuse.
49
51
 
50
- Use a `task` draft for `timeoutSeconds` or reuse:
52
+ `timeoutSeconds` also works in the shorthand and in a `tasks[]` entry; use a
53
+ `task` draft for reuse:
51
54
 
52
55
  ```json
53
- {"operation":"run","task":{"timeoutSeconds":3600,"action":{"type":"agent","session":{"mode":"reuse","sessionId":"..."},"prompt":"Check the result"}}}
56
+ {"operation":"run","task":{"timeoutSeconds":7200,"action":{"type":"agent","session":{"mode":"reuse","sessionId":"..."},"prompt":"Check the result"}}}
54
57
  ```
55
58
 
56
- Inline drafts may omit `trigger`; only `manual` is allowed. Their nested
57
- `callback` is ignored: use top-level delivery options above. Nested callbacks
58
- matter for saved schedules (below).
59
+ Inline drafts may omit `trigger`; only `manual` is allowed. A nested
60
+ `callback` is refused: use the top-level delivery options above. Nested
61
+ callbacks matter for saved schedules (below).
59
62
 
60
63
  ## Groups and chains
61
64
 
62
- `tasks[]` needs 2+ entries: prompt strings, `{prompt,cwd?,launch?,name?}`, full
63
- drafts, or `{task_id}`. Do not combine it with `task`, `task_id` or `session_mode`.
65
+ `tasks[]` needs 2+ entries: prompt strings,
66
+ `{prompt,cwd?,launch?,name?,timeoutSeconds?}`, full drafts, or `{task_id}`. Do not combine it with `task`, `task_id` or `session_mode`.
64
67
 
65
68
  ```json
66
69
  {"operation":"run","tasks":["Review correctness","Review test gaps"],"join":"all"}
@@ -93,13 +96,15 @@ available IDs by callback; invalid `launch.thinking` fails the call.
93
96
 
94
97
  ## Control and decisions
95
98
 
96
- `message` must be non-empty, <16 KiB. Controls require ownership: your delegated
99
+ `message` must be non-empty, at most 16 KiB. Controls require ownership: your delegated
97
100
  trees, or only descendants when you are a subagent.
98
101
 
99
102
  - `steer`: interrupt a child with corrections.
100
103
  - `follow_up`: queue guidance after its current turn.
101
104
  - `resume`: terminal run only; same session, new run ID/callback, same depth,
102
- message as prompt. Expires its unanswered decision.
105
+ message as prompt. Expires its unanswered decision. Being a new run, it takes
106
+ the same `callback` and `callback_session_id` as `run`, under the same rule:
107
+ a subagent may not redirect them.
103
108
  - `cancel`: run or group; cascades to descendants, terminal runs unchanged.
104
109
 
105
110
  `steer`/`follow_up` require a non-terminal run; undelivered guidance expires when
@@ -151,12 +156,15 @@ draft, including `trigger`. Schedules notify only with nested
151
156
  which scheduled runs lack.
152
157
 
153
158
  - Inside a run: Agent actions only (stored/inline), no reuse or create/update.
154
- - Depth 0–2: at most 3 runs below the invoking session; a fourth errors. Each
155
- root allows 16 descendant runs (depth ≥1, resumes included). Your direct
159
+ - Depth 0–2: three levels of nesting below the invoking session; a fourth
160
+ level errors. Each root allows 16 descendant runs (depth ≥1, resumes included). Your direct
156
161
  children are separate roots and do not count toward that limit.
157
- - 4 Agent runs execute instance-wide; others queue without error. Timeout
158
- starts **at enqueue**, so a run can time out before starting.
159
- - Default timeout 900s; `timeoutSeconds:1–86400` in draft form only. Timeout
160
- reports `failed / task timed out`.
162
+ - 6 Agent runs execute instance-wide; others queue without error. A queued run
163
+ waits **unbounded**, ended only by cancellation or a restart.
164
+ - Default timeout 3600s; `timeoutSeconds:1–86400` in a draft, the `prompt`
165
+ shorthand or a `tasks[]` entry. It starts when the run does, not at enqueue,
166
+ and reports `failed / task timed out`.
161
167
  - Restart marks queued/running runs `interrupted`; callbacks still apply.
162
168
  During drain, new roots are refused: retry after restart.
169
+ - Watch probes that matched nothing are kept only 50 deep per watch; older
170
+ ones are deleted unless a message, a pending callback or a resume needs them.
@@ -1,84 +0,0 @@
1
- // Fetch one configuration document from the operator's source URL: HTTPS with
2
- // no credentials, redirects followed while they stay HTTPS, JSON capped at
3
- // 1 MiB so a hostile or broken source cannot exhaust memory.
4
- export const CONFIG_SYNC_BYTES = 1024 * 1024;
5
- const MAX_REDIRECTS = 5;
6
- export function configSourceUrl(raw) {
7
- let url;
8
- try {
9
- url = new URL(raw.trim());
10
- }
11
- catch {
12
- throw new Error("A valid HTTPS source URL is required");
13
- }
14
- if (url.protocol !== "https:" || url.username || url.password || url.hash || raw.length > 4096) {
15
- throw new Error("Source must be HTTPS, without credentials or a fragment");
16
- }
17
- return url;
18
- }
19
- export async function downloadConfig(raw, etag, signal) {
20
- let url = configSourceUrl(raw);
21
- for (let hop = 0;; hop++) {
22
- signal.throwIfAborted();
23
- let res;
24
- try {
25
- res = await fetch(url, {
26
- method: "GET", redirect: "manual", signal,
27
- headers: { accept: "application/json", ...(etag ? { "if-none-match": etag } : {}) },
28
- });
29
- }
30
- catch {
31
- throw new Error(signal.aborted ? "Configuration download timed out or was cancelled" : "Could not connect to source");
32
- }
33
- const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
34
- if (location) {
35
- await res.body?.cancel().catch(() => { });
36
- if (hop >= MAX_REDIRECTS)
37
- throw new Error("Source redirected too many times");
38
- try {
39
- url = configSourceUrl(new URL(location, url).href);
40
- }
41
- catch {
42
- throw new Error("Source redirected to a location that is not HTTPS");
43
- }
44
- continue;
45
- }
46
- if (res.status !== 200 && res.status !== 304) {
47
- await res.body?.cancel().catch(() => { });
48
- throw new Error(res.status === 404 || res.status === 410
49
- ? "Source link was revoked or does not exist"
50
- : `Source returned HTTP ${String(res.status)}`);
51
- }
52
- const status = res.status === 304 ? 304 : 200;
53
- if (status === 200 && !/^application\/json(?:\s*;|$)/i.test(res.headers.get("content-type") ?? "")) {
54
- await res.body?.cancel().catch(() => { });
55
- throw new Error("Source did not return JSON");
56
- }
57
- return { status, etag: res.headers.get("etag"), body: await read(res, signal) };
58
- }
59
- }
60
- async function read(res, signal) {
61
- const reader = res.body?.getReader();
62
- if (!reader)
63
- return "";
64
- const chunks = [];
65
- let size = 0;
66
- try {
67
- for (;;) {
68
- const { done, value } = await reader.read();
69
- if (done)
70
- break;
71
- size += value.length;
72
- if (size > CONFIG_SYNC_BYTES)
73
- throw new Error("Configuration exceeds 1 MiB");
74
- chunks.push(Buffer.from(value));
75
- }
76
- }
77
- catch (err) {
78
- await reader.cancel().catch(() => { });
79
- if (err instanceof Error && err.message === "Configuration exceeds 1 MiB")
80
- throw err;
81
- throw new Error(signal.aborted ? "Configuration download timed out or was cancelled" : "Configuration download failed");
82
- }
83
- return Buffer.concat(chunks).toString("utf8");
84
- }
package/dist/limits.js DELETED
@@ -1,14 +0,0 @@
1
- // The numbers more than one area has to agree on.
2
- //
3
- // Not policy or behaviour — a value several modules must spell the same way,
4
- // and a wrong copy makes two surfaces disagree about one session: a title
5
- // truncated to a different length depending on which path derived it.
6
- //
7
- // Here because a leaf may be imported by every area and depends on nothing
8
- // itself, which is the only shape that fits: agent/ derives a title and must
9
- // not import core/, web/ derives one too and must not import agent/, and the
10
- // browser needs the same numbers with no runtime behind them.
11
- /** How much of a message becomes a title, wherever one is derived: the listing
12
- * reading a transcript (agent/listing.ts), a rename's fallback (agent/pi.ts),
13
- * the fill at first prompt and the rename boundary (web/). */
14
- export const SESSION_TITLE_MAX = 80;