@timqi/pier 0.0.29 → 0.1.1

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 (165) hide show
  1. package/README.md +58 -125
  2. package/dist/agent/config.js +24 -11
  3. package/dist/agent/credentials.js +11 -23
  4. package/dist/agent/events.js +43 -62
  5. package/dist/agent/listing.js +113 -68
  6. package/dist/agent/pi.js +204 -211
  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 +8 -24
  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 +13 -35
  45. package/dist/core/queue.js +3 -5
  46. package/dist/core/reply.js +41 -142
  47. package/dist/core/router.js +209 -260
  48. package/dist/core/types.js +17 -1
  49. package/dist/db.js +98 -252
  50. package/dist/drain.js +58 -51
  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 +6 -14
  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 +87 -179
  63. package/dist/paths.js +10 -26
  64. package/dist/secrets.js +19 -46
  65. package/dist/service.js +33 -75
  66. package/dist/settings.js +42 -65
  67. package/dist/tasks/agent.js +129 -114
  68. package/dist/tasks/callbacks.js +9 -19
  69. package/dist/tasks/command.js +29 -14
  70. package/dist/tasks/definitions.js +39 -62
  71. package/dist/tasks/execution.js +46 -42
  72. package/dist/tasks/groups.js +41 -35
  73. package/dist/tasks/messages.js +121 -182
  74. package/dist/tasks/outbox.js +61 -55
  75. package/dist/tasks/routes.js +5 -11
  76. package/dist/tasks/runs.js +14 -13
  77. package/dist/tasks/service.js +53 -54
  78. package/dist/tasks/store.js +53 -30
  79. package/dist/tasks/tool.js +132 -61
  80. package/dist/tools-task.js +20 -60
  81. package/dist/tools.js +100 -327
  82. package/dist/update.js +21 -44
  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 +45 -85
  89. package/dist/web/providers.js +14 -13
  90. package/dist/web/public/assets/{activity-D3m4L2IL.js → activity-B89_hH7q.js} +2 -2
  91. package/dist/web/public/assets/activity-B89_hH7q.js.br +0 -0
  92. package/dist/web/public/assets/activity-B89_hH7q.js.gz +0 -0
  93. package/dist/web/public/assets/boards-BeKW0ZXK.js +1 -0
  94. package/dist/web/public/assets/boards-BeKW0ZXK.js.br +0 -0
  95. package/dist/web/public/assets/boards-BeKW0ZXK.js.gz +0 -0
  96. package/dist/web/public/assets/explorer-DIuMlaV3.js +4 -0
  97. package/dist/web/public/assets/explorer-DIuMlaV3.js.br +0 -0
  98. package/dist/web/public/assets/explorer-DIuMlaV3.js.gz +0 -0
  99. package/dist/web/public/assets/index-DzXDXra_.js +85 -0
  100. package/dist/web/public/assets/index-DzXDXra_.js.br +0 -0
  101. package/dist/web/public/assets/index-DzXDXra_.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-Cwy0mN8i.js +1 -0
  106. package/dist/web/public/assets/runs-Cwy0mN8i.js.br +0 -0
  107. package/dist/web/public/assets/runs-Cwy0mN8i.js.gz +0 -0
  108. package/dist/web/public/assets/settings-DzZLmujq.js +5 -0
  109. package/dist/web/public/assets/settings-DzZLmujq.js.br +0 -0
  110. package/dist/web/public/assets/settings-DzZLmujq.js.gz +0 -0
  111. package/dist/web/public/assets/task-runs-BCakxFk8.js +3 -0
  112. package/dist/web/public/assets/task-runs-BCakxFk8.js.br +0 -0
  113. package/dist/web/public/assets/task-runs-BCakxFk8.js.gz +0 -0
  114. package/dist/web/public/assets/tasks-BlzEbk11.js +4 -0
  115. package/dist/web/public/assets/tasks-BlzEbk11.js.br +0 -0
  116. package/dist/web/public/assets/tasks-BlzEbk11.js.gz +0 -0
  117. package/dist/web/public/index.html +100 -130
  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/manifest.webmanifest +2 -2
  121. package/dist/web/public/manifest.webmanifest.br +0 -0
  122. package/dist/web/public/manifest.webmanifest.gz +0 -0
  123. package/dist/web/public/sw.js +14 -2
  124. package/dist/web/public/sw.js.br +0 -0
  125. package/dist/web/public/sw.js.gz +0 -0
  126. package/dist/web/push.js +55 -77
  127. package/dist/web/route.js +3 -7
  128. package/dist/web/server.js +131 -180
  129. package/dist/web/session-state.js +14 -54
  130. package/dist/web/types.js +2 -4
  131. package/dist/web/webpush.js +10 -25
  132. package/docs/deploy.md +115 -330
  133. package/package.json +2 -1
  134. package/skills/pier-boards/SKILL.md +81 -160
  135. package/skills/pier-help/SKILL.md +23 -20
  136. package/skills/pier-slack/SKILL.md +2 -2
  137. package/skills/pier-tasks/SKILL.md +153 -160
  138. package/dist/config-sync-fetch.js +0 -84
  139. package/dist/limits.js +0 -14
  140. package/dist/web/public/assets/activity-D3m4L2IL.js.br +0 -0
  141. package/dist/web/public/assets/activity-D3m4L2IL.js.gz +0 -0
  142. package/dist/web/public/assets/boards-BIObcQeX.js +0 -1
  143. package/dist/web/public/assets/boards-BIObcQeX.js.br +0 -0
  144. package/dist/web/public/assets/boards-BIObcQeX.js.gz +0 -0
  145. package/dist/web/public/assets/explorer-C_rSWPNB.js +0 -4
  146. package/dist/web/public/assets/explorer-C_rSWPNB.js.br +0 -0
  147. package/dist/web/public/assets/explorer-C_rSWPNB.js.gz +0 -0
  148. package/dist/web/public/assets/index-CX3fYZY5.css +0 -2
  149. package/dist/web/public/assets/index-CX3fYZY5.css.br +0 -0
  150. package/dist/web/public/assets/index-CX3fYZY5.css.gz +0 -0
  151. package/dist/web/public/assets/index-uFsZkKOQ.js +0 -85
  152. package/dist/web/public/assets/index-uFsZkKOQ.js.br +0 -0
  153. package/dist/web/public/assets/index-uFsZkKOQ.js.gz +0 -0
  154. package/dist/web/public/assets/runs-Ch6DZq6O.js +0 -1
  155. package/dist/web/public/assets/runs-Ch6DZq6O.js.br +0 -0
  156. package/dist/web/public/assets/runs-Ch6DZq6O.js.gz +0 -0
  157. package/dist/web/public/assets/settings-BWcEIEcv.js +0 -5
  158. package/dist/web/public/assets/settings-BWcEIEcv.js.br +0 -0
  159. package/dist/web/public/assets/settings-BWcEIEcv.js.gz +0 -0
  160. package/dist/web/public/assets/task-runs-DPkwv2UE.js +0 -3
  161. package/dist/web/public/assets/task-runs-DPkwv2UE.js.br +0 -0
  162. package/dist/web/public/assets/task-runs-DPkwv2UE.js.gz +0 -0
  163. package/dist/web/public/assets/tasks-DTiCi2mH.js +0 -4
  164. package/dist/web/public/assets/tasks-DTiCi2mH.js.br +0 -0
  165. package/dist/web/public/assets/tasks-DTiCi2mH.js.gz +0 -0
@@ -1,43 +1,13 @@
1
1
  // What the workbench decided about a session, and nothing a transcript already
2
- // knows. A finished turn nobody has looked at (`unread`), and the rail's
3
- // working set: the rank of a row kept on top (`sort`).
4
- //
5
- // The working set replaced pinning and dragging. Ordering the rail by activity
6
- // made it jump — a scheduled task, a subagent, anyone's Slack message moved the
7
- // row somebody was about to click — and ordering it by hand asked for the
8
- // arranging. So: a session enters the set at the front when a human speaks to
9
- // it and it is not in the set already, and members never move relative to each
10
- // other until one is pushed out of the last slot. A three-week-old session is
11
- // back on top the moment it is used; the four you switch between hold still.
12
- //
13
- // Remembered rather than derived on purpose: "the last time a human spoke" is
14
- // there in the transcript and still jumps, because it reorders on every
15
- // message. Only an order nothing recomputes can hold still.
16
- //
17
- // It used to mirror the summary too — cwd, title, created_at, last_active —
18
- // because listing sessions meant parsing every transcript on disk (~237ms) and
19
- // the rail could not pay that on every read. agent/listing.ts made a listing
20
- // cheap, so the mirror bought nothing and cost two stores kept in step: a
21
- // backfill gate, a repair pass on every full listing, a touch at the end of
22
- // every turn, a title write on the first prompt, a second write on rename.
23
- // Deriving beats syncing; all of it is gone.
24
- //
25
- // `cwd` and `project_sort` stayed as columns nothing reads: they keyed the
26
- // per-directory order the rail no longer has (migration 19).
27
- //
28
- // The rank does not bring that back: it is written only when a session outside
29
- // the set is spoken to, not once per turn.
30
- //
31
- // One row per session rather than two JSON files: the unread flag is written at
32
- // the end of every turn, and rewriting a whole file on each of those writes
33
- // loses the entire set when the process dies mid-write — a truncated file reads
34
- // back as "nothing on top", which is indistinguishable from a fresh install.
2
+ // knows: a finished turn nobody looked at (`unread`), and the rail's working
3
+ // set (`sort`). A session enters the set at the front when a human speaks to it
4
+ // and it is not already in; members never move relative to each other until one
5
+ // is pushed out. Remembered, not derived: any order recomputed from activity
6
+ // jumps on every message. `cwd` and `project_sort` are columns nothing reads.
35
7
  import { pierDb, transact } from "../db.js";
36
- /** How many rows the rail keeps on top. Larger than the handful anybody
37
- * switches between (3–6), and small enough that every member is somewhere a
38
- * hand can see: a set as deep as the page (20) would count a row nobody has
39
- * scrolled to as "already up there" and never bring it back. */
40
- export const WORKING_SET = 8;
8
+ /** Small enough that every member is where a hand can see it: a set as deep as
9
+ * the page would count a row nobody scrolled to as "already up there". */
10
+ export const WORKING_SET = 5;
41
11
  export class SessionStateStore {
42
12
  #db;
43
13
  constructor(db = pierDb()) {
@@ -51,14 +21,10 @@ export class SessionStateStore {
51
21
  this.#db.prepare(`INSERT INTO session_state(session_id, unread) VALUES (?, ?)
52
22
  ON CONFLICT(session_id) DO UPDATE SET unread = excluded.unread`).run(sessionId, unread ? 1 : 0);
53
23
  }
54
- /** A human spoke to this session. Already in the working set → nothing moves
55
- * and nothing is written; otherwise it takes the front slot and whatever
56
- * sat in the last one falls out. Answers whether the order changed, so the
57
- * caller broadcasts a re-list only when there is something to see. */
24
+ /** Already in the working set: nothing moves. Answers whether the order
25
+ * changed, so the caller re-lists only when there is something to see. */
58
26
  promote(sessionId) {
59
- // `session_id` breaks a tie the migration can leave behind: several pinned
60
- // rows nobody ever dragged share rank -1 until the first promotion
61
- // renumbers them.
27
+ // `session_id` breaks a tie migration 20 can leave behind (several rows at -1).
62
28
  const ranked = this.#db.prepare("SELECT session_id AS id FROM session_state WHERE sort IS NOT NULL ORDER BY sort, session_id").all().map((r) => r.id);
63
29
  if (ranked.includes(sessionId))
64
30
  return false;
@@ -67,8 +33,7 @@ export class SessionStateStore {
67
33
  const rank = this.#db.prepare(`INSERT INTO session_state(session_id, sort) VALUES (?, ?)
68
34
  ON CONFLICT(session_id) DO UPDATE SET sort = excluded.sort`);
69
35
  const drop = this.#db.prepare("UPDATE session_state SET sort = NULL WHERE session_id = ?");
70
- // Every rank rewritten rather than the new one prepended: the places are
71
- // 0..n and a half-renumbered set is an order nobody would recognize.
36
+ // Every rank rewritten: a half-renumbered set is an order nobody would recognize.
72
37
  transact(this.#db, () => {
73
38
  kept.forEach((id, i) => rank.run(id, i));
74
39
  for (const id of evicted)
@@ -76,16 +41,11 @@ export class SessionStateStore {
76
41
  });
77
42
  return true;
78
43
  }
79
- /** Drop a session's organization row entirely — rank and unread. For
80
- * ghosts: Pi persists a session only once its first assistant reply lands,
81
- * so a created-and-never-messaged one cannot be resumed, and its remembered
82
- * row would otherwise sit in the rail 404ing forever — holding a slot in the
83
- * working set while it does. */
44
+ /** For ghosts: a created-and-never-messaged session cannot be resumed, and
45
+ * its row would otherwise hold a working-set slot while 404ing forever. */
84
46
  forget(sessionId) {
85
47
  this.#db.prepare("DELETE FROM session_state WHERE session_id = ?").run(sessionId);
86
48
  }
87
- /** What this store knows about the sessions it knows anything about, for a
88
- * caller holding the listing. */
89
49
  flags() {
90
50
  const rows = this.#db.prepare(`SELECT session_id AS id, unread, sort
91
51
  FROM session_state WHERE unread = 1 OR sort IS NOT NULL`).all();
package/dist/web/types.js CHANGED
@@ -1,5 +1,3 @@
1
- // The shapes web's HTTP answers carry beyond the stores they come from — the
2
- // area's own wire vocabulary, and the one file in web/ the browser may import
3
- // type-only (docs/architecture.md, same rule as tasks/types.ts). Nothing here
4
- // runs: no imports, no values, so a page pays nothing for reading it.
1
+ // The area's own wire vocabulary, and the one file in web/ the browser may
2
+ // import type-only (docs/architecture.md). No imports, no values.
5
3
  export {};
@@ -1,19 +1,13 @@
1
- // The Web Push wire format, and only that: RFC 8291 message encryption
2
- // (ECDH P-256 → HKDF → one aes128gcm record) and RFC 8292 VAPID
3
- // authorization. Who is notified, and why, is push.ts.
4
- //
5
- // Written on node:crypto instead of pulled in: the whole format is one ECDH,
6
- // two HKDFs, one AES-GCM record and a JWT, and every step of it has a
7
- // published test vector (webpush.test.ts runs the RFC's). A dependency here
8
- // would be new transitive code inside the process that holds the operator's
9
- // provider keys, buying ~120 lines (principle 8).
1
+ // The Web Push wire format: RFC 8291 message encryption (ECDH P-256 → HKDF →
2
+ // one aes128gcm record) and RFC 8292 VAPID. On node:crypto rather than a
3
+ // dependency: every step has a published test vector, and a dependency here is
4
+ // transitive code inside the process holding the provider keys (principle 8).
10
5
  import { createCipheriv, createECDH, createPrivateKey, hkdfSync, randomBytes, sign } from "node:crypto";
11
6
  /** RFC 8291 §4: one record, and a push service need not accept more than 4096
12
7
  * octets of body. Header (86) + padding (1) + GCM tag (16) leaves this. */
13
8
  export const MAX_PUSH_PLAINTEXT = 3993;
14
9
  const RECORD_SIZE = 4096;
15
- /** VAPID token lifetime. Apple refuses anything past 24h; half a day is well
16
- * inside every push service's limit and still outlives a slow retry. */
10
+ /** Apple refuses anything past 24h. */
17
11
  const TOKEN_TTL_S = 12 * 60 * 60;
18
12
  const REQUEST_TIMEOUT_MS = 10_000;
19
13
  const b64 = (b) => Buffer.from(b instanceof ArrayBuffer ? new Uint8Array(b) : b).toString("base64url");
@@ -29,13 +23,8 @@ export function generateVapidKeys() {
29
23
  privateKey: b64(pad32(ecdh.getPrivateKey())),
30
24
  };
31
25
  }
32
- /**
33
- * Encrypt one push message for `target` (RFC 8291 §3.4, RFC 8188 header).
34
- *
35
- * `salt` and `serverKey` are injectable for exactly one reason: the RFC's
36
- * worked example is the only way to prove this implementation is right, and it
37
- * fixes both. Nothing else may pass them — a reused salt is a broken cipher.
38
- */
26
+ /** RFC 8291 §3.4, RFC 8188 header. `salt` and `serverKey` are injectable only
27
+ * for the RFC's worked example; a reused salt is a broken cipher. */
39
28
  export function encryptPush(plaintext, target, { salt = randomBytes(16), serverKey } = {}) {
40
29
  const body = Buffer.from(plaintext);
41
30
  if (body.length > MAX_PUSH_PLAINTEXT) {
@@ -68,9 +57,7 @@ export function encryptPush(plaintext, target, { salt = randomBytes(16), serverK
68
57
  header.writeUInt8(asPublic.length, 20);
69
58
  return Buffer.concat([header, asPublic, sealed]);
70
59
  }
71
- /** The `Authorization` a push service checks before it accepts anything: a
72
- * short-lived ES256 JWT bound to the service's own origin, plus the public
73
- * key the subscription was created with (RFC 8292 §3). */
60
+ /** RFC 8292 §3: an ES256 JWT bound to the service's origin, plus the public key. */
74
61
  export function vapidAuthorization(endpoint, keys, subject, now = Date.now()) {
75
62
  const token = [
76
63
  b64(Buffer.from(JSON.stringify({ typ: "JWT", alg: "ES256" }))),
@@ -95,8 +82,7 @@ export function vapidAuthorization(endpoint, keys, subject, now = Date.now()) {
95
82
  const signature = sign("sha256", Buffer.from(token), { key, dsaEncoding: "ieee-p1363" });
96
83
  return `vapid t=${token}.${b64(signature)}, k=${keys.publicKey}`;
97
84
  }
98
- /** POST one encrypted message. Never throws: every outcome is a result the
99
- * caller can log or act on. */
85
+ /** Never throws: every outcome is a result the caller can log or act on. */
100
86
  export async function sendPush(target, payload, keys, subject, ttlSeconds = 4 * 60 * 60, fetchImpl = fetch) {
101
87
  let body;
102
88
  try {
@@ -120,8 +106,7 @@ export async function sendPush(target, payload, keys, subject, ttlSeconds = 4 *
120
106
  });
121
107
  if (res.ok)
122
108
  return { status: res.status };
123
- // The service's own sentence is the only thing that explains a 400 from
124
- // Apple or a 403 from FCM; without it the operator sees a bare number.
109
+ // The service's own sentence is the only thing that explains a 400 from Apple.
125
110
  const said = (await res.text().catch(() => "")).slice(0, 200);
126
111
  return { status: res.status, error: said || res.statusText };
127
112
  }