@timqi/pier 0.1.0 → 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.
- package/README.md +58 -125
- package/dist/agent/config.js +6 -15
- package/dist/agent/credentials.js +11 -23
- package/dist/agent/events.js +42 -64
- package/dist/agent/listing.js +39 -92
- package/dist/agent/pi.js +103 -252
- package/dist/boards/boards.js +19 -29
- package/dist/channels/attach.js +14 -42
- package/dist/channels/chains.js +33 -37
- package/dist/channels/chunk.js +8 -28
- package/dist/channels/commands.js +3 -14
- package/dist/channels/config.js +33 -52
- package/dist/channels/control.js +4 -13
- package/dist/channels/conversations.js +8 -25
- package/dist/channels/dedup.js +8 -17
- package/dist/channels/gatekeeper.js +13 -23
- package/dist/channels/lark-api.js +23 -63
- package/dist/channels/lark-outbound.js +12 -44
- package/dist/channels/lark-panel.js +7 -23
- package/dist/channels/lark-render.js +18 -62
- package/dist/channels/lark.js +52 -141
- package/dist/channels/lines.js +13 -15
- package/dist/channels/panel.js +16 -36
- package/dist/channels/receipts.js +29 -52
- package/dist/channels/routes.js +3 -9
- package/dist/channels/runtime.js +12 -23
- package/dist/channels/slack-api.js +34 -86
- package/dist/channels/slack-directory.js +7 -23
- package/dist/channels/slack-outbound.js +12 -56
- package/dist/channels/slack-panel.js +4 -13
- package/dist/channels/slack-render.js +23 -91
- package/dist/channels/slack-tool.js +48 -171
- package/dist/channels/slack.js +73 -239
- package/dist/channels/telegram-api.js +8 -20
- package/dist/channels/telegram-panel.js +5 -21
- package/dist/channels/telegram-render.js +13 -40
- package/dist/channels/telegram.js +54 -146
- package/dist/channels/types.js +5 -16
- package/dist/cli.js +17 -41
- package/dist/config-sync.js +87 -4
- package/dist/core/hub.js +7 -20
- package/dist/core/identity.js +20 -59
- package/dist/core/inbound-file.js +15 -49
- package/dist/core/inbox.js +12 -34
- package/dist/core/queue.js +3 -5
- package/dist/core/reply.js +41 -142
- package/dist/core/router.js +209 -264
- package/dist/core/types.js +4 -0
- package/dist/db.js +88 -272
- package/dist/drain.js +57 -50
- package/dist/extensions/index.js +3 -11
- package/dist/extensions/web/anthropic.js +3 -9
- package/dist/extensions/web/artifacts.js +2 -5
- package/dist/extensions/web/content.js +4 -12
- package/dist/extensions/web/http.js +2 -6
- package/dist/extensions/web/language.js +8 -18
- package/dist/extensions/web/openai.js +1 -1
- package/dist/extensions/web/provider.js +5 -18
- package/dist/extensions/web/tools.js +19 -63
- package/dist/lock.js +98 -0
- package/dist/log.js +9 -26
- package/dist/main.js +84 -183
- package/dist/paths.js +10 -26
- package/dist/secrets.js +18 -45
- package/dist/service.js +31 -73
- package/dist/settings.js +19 -63
- package/dist/tasks/agent.js +24 -45
- package/dist/tasks/callbacks.js +8 -16
- package/dist/tasks/command.js +2 -6
- package/dist/tasks/definitions.js +39 -62
- package/dist/tasks/execution.js +41 -39
- package/dist/tasks/groups.js +8 -11
- package/dist/tasks/messages.js +88 -155
- package/dist/tasks/outbox.js +33 -54
- package/dist/tasks/routes.js +4 -7
- package/dist/tasks/runs.js +4 -9
- package/dist/tasks/service.js +29 -42
- package/dist/tasks/store.js +36 -27
- package/dist/tasks/tool.js +59 -60
- package/dist/tools-task.js +20 -60
- package/dist/tools.js +98 -325
- package/dist/update.js +20 -43
- package/dist/web/auth.js +118 -179
- package/dist/web/config-sync.js +2 -2
- package/dist/web/config.js +3 -7
- package/dist/web/explorer.js +10 -21
- package/dist/web/fs.js +20 -42
- package/dist/web/instance.js +35 -82
- package/dist/web/providers.js +5 -11
- package/dist/web/public/assets/{activity-Bl3vZukb.js → activity-B89_hH7q.js} +1 -1
- package/dist/web/public/assets/activity-B89_hH7q.js.br +0 -0
- package/dist/web/public/assets/activity-B89_hH7q.js.gz +0 -0
- package/dist/web/public/assets/{boards-DYuf4Mlj.js → boards-BeKW0ZXK.js} +1 -1
- package/dist/web/public/assets/boards-BeKW0ZXK.js.br +0 -0
- package/dist/web/public/assets/boards-BeKW0ZXK.js.gz +0 -0
- package/dist/web/public/assets/explorer-DIuMlaV3.js +4 -0
- package/dist/web/public/assets/explorer-DIuMlaV3.js.br +0 -0
- package/dist/web/public/assets/explorer-DIuMlaV3.js.gz +0 -0
- package/dist/web/public/assets/index-DzXDXra_.js +85 -0
- package/dist/web/public/assets/index-DzXDXra_.js.br +0 -0
- package/dist/web/public/assets/index-DzXDXra_.js.gz +0 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css +2 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css.br +0 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css.gz +0 -0
- package/dist/web/public/assets/{runs-BLJu7EXN.js → runs-Cwy0mN8i.js} +1 -1
- package/dist/web/public/assets/runs-Cwy0mN8i.js.br +0 -0
- package/dist/web/public/assets/runs-Cwy0mN8i.js.gz +0 -0
- package/dist/web/public/assets/{settings-BrdVh-Zi.js → settings-DzZLmujq.js} +1 -1
- package/dist/web/public/assets/settings-DzZLmujq.js.br +0 -0
- package/dist/web/public/assets/settings-DzZLmujq.js.gz +0 -0
- package/dist/web/public/assets/{task-runs-CeQS1rxa.js → task-runs-BCakxFk8.js} +1 -1
- package/dist/web/public/assets/task-runs-BCakxFk8.js.br +0 -0
- package/dist/web/public/assets/task-runs-BCakxFk8.js.gz +0 -0
- package/dist/web/public/assets/tasks-BlzEbk11.js +4 -0
- package/dist/web/public/assets/tasks-BlzEbk11.js.br +0 -0
- package/dist/web/public/assets/tasks-BlzEbk11.js.gz +0 -0
- package/dist/web/public/index.html +30 -16
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/sw.js +14 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/push.js +55 -77
- package/dist/web/route.js +3 -7
- package/dist/web/server.js +109 -190
- package/dist/web/session-state.js +13 -53
- package/dist/web/types.js +2 -4
- package/dist/web/webpush.js +10 -25
- package/docs/deploy.md +115 -330
- package/package.json +1 -1
- package/skills/pier-boards/SKILL.md +81 -160
- package/skills/pier-help/SKILL.md +23 -20
- package/skills/pier-slack/SKILL.md +2 -2
- package/skills/pier-tasks/SKILL.md +23 -15
- package/dist/config-sync-fetch.js +0 -84
- package/dist/limits.js +0 -14
- package/dist/web/public/assets/activity-Bl3vZukb.js.br +0 -0
- package/dist/web/public/assets/activity-Bl3vZukb.js.gz +0 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js.br +0 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js.gz +0 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js +0 -4
- package/dist/web/public/assets/explorer-qJH_9nTE.js.br +0 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js.gz +0 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js +0 -85
- package/dist/web/public/assets/index-Dqdb-Eqt.js.br +0 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js.gz +0 -0
- package/dist/web/public/assets/index-DzmMzvi_.css +0 -2
- package/dist/web/public/assets/index-DzmMzvi_.css.br +0 -0
- package/dist/web/public/assets/index-DzmMzvi_.css.gz +0 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js.br +0 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js.gz +0 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js.br +0 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js.gz +0 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js.br +0 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js.gz +0 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js +0 -4
- package/dist/web/public/assets/tasks-bcb3fYdK.js.br +0 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js.gz +0 -0
|
@@ -1,42 +1,12 @@
|
|
|
1
1
|
// What the workbench decided about a session, and nothing a transcript already
|
|
2
|
-
// knows
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
* set as deep as the page (20) would count a row nobody has scrolled to as
|
|
39
|
-
* "already up there" and never bring it back. */
|
|
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". */
|
|
40
10
|
export const WORKING_SET = 5;
|
|
41
11
|
export class SessionStateStore {
|
|
42
12
|
#db;
|
|
@@ -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
|
-
/**
|
|
55
|
-
*
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
80
|
-
*
|
|
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
|
|
2
|
-
//
|
|
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 {};
|
package/dist/web/webpush.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
// The Web Push wire format
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
}
|