@timqi/pier 0.0.5 → 0.0.6
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 +1 -0
- package/dist/agent/events.js +6 -1
- package/dist/agent/pi.js +28 -3
- package/dist/channels/chunk.js +34 -0
- package/dist/channels/control.js +6 -12
- package/dist/channels/dedup.js +45 -0
- package/dist/channels/lark-api.js +233 -0
- package/dist/channels/lark-outbound.js +101 -0
- package/dist/channels/lark-panel.js +95 -0
- package/dist/channels/lark-render.js +107 -0
- package/dist/channels/lark.js +501 -0
- package/dist/channels/lines.js +19 -0
- package/dist/channels/panel.js +4 -0
- package/dist/channels/receipts.js +15 -0
- package/dist/channels/routes.js +0 -1
- package/dist/channels/runtime.js +5 -1
- package/dist/channels/slack-api.js +4 -2
- package/dist/channels/slack-panel.js +3 -6
- package/dist/channels/slack-render.js +3 -23
- package/dist/channels/slack.js +39 -72
- package/dist/channels/telegram-api.js +4 -2
- package/dist/channels/telegram-panel.js +3 -3
- package/dist/channels/telegram.js +55 -51
- package/dist/channels/types.js +9 -0
- package/dist/core/inbox.js +67 -1
- package/dist/core/types.js +4 -0
- package/dist/db.js +6 -0
- package/dist/main.js +5 -0
- package/dist/web/auth.js +36 -9
- package/dist/web/public/assets/__vite-browser-external-2447137e-BvRk9kiK.js +0 -0
- package/dist/web/public/assets/ghostty-web-ODXT71Ln.js +13 -0
- package/dist/web/public/assets/index-BUNGxtMe.css +2 -0
- package/dist/web/public/assets/{index-BAW9Nhaa.js → index-QPYgeBhQ.js} +13 -13
- package/dist/web/public/index.html +10 -2
- package/dist/web/server.js +86 -19
- package/dist/web/session-state.js +59 -25
- package/dist/web/terminal.js +334 -0
- package/docs/deploy.md +5 -1
- package/package.json +10 -2
- package/dist/web/public/assets/index-CwBoxtXP.css +0 -2
package/dist/core/types.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Normative seam types — THIS FILE is the system contract (docs/architecture.md
|
|
2
2
|
// documents the rules around it). Changing a seam is a design decision, not a
|
|
3
3
|
// refactor; keep it implementable over RPC (no Pi types may appear here).
|
|
4
|
+
/** How much of a tool result any surface ever shows. A transcript replay
|
|
5
|
+
* carries no more than that: a session's tool output is most of its history
|
|
6
|
+
* payload, and the bytes past this point were downloaded to be sliced off. */
|
|
7
|
+
export const MAX_STEP_OUTPUT = 8_000;
|
|
4
8
|
/** Every level Pi accepts, in order. The union is derived so the two cannot
|
|
5
9
|
* drift, and boundary validators use isThinkingLevel instead of their own copy. */
|
|
6
10
|
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
package/dist/db.js
CHANGED
|
@@ -139,6 +139,12 @@ const MIGRATIONS = [
|
|
|
139
139
|
note TEXT NOT NULL,
|
|
140
140
|
created_at INTEGER NOT NULL
|
|
141
141
|
);
|
|
142
|
+
`,
|
|
143
|
+
// 4 — Projects can render from SQLite without scanning every Pi transcript.
|
|
144
|
+
`
|
|
145
|
+
ALTER TABLE session_state ADD COLUMN cwd TEXT;
|
|
146
|
+
ALTER TABLE session_state ADD COLUMN title TEXT;
|
|
147
|
+
ALTER TABLE session_state ADD COLUMN created_at INTEGER;
|
|
142
148
|
`,
|
|
143
149
|
];
|
|
144
150
|
let shared;
|
package/dist/main.js
CHANGED
|
@@ -34,6 +34,7 @@ import { startAutoUpdate, UpdateCheck } from "./update.js";
|
|
|
34
34
|
import { AuthStore, registerAuthRoutes, requireAuth } from "./web/auth.js";
|
|
35
35
|
import { SessionStateStore } from "./web/session-state.js";
|
|
36
36
|
import { createServer } from "./web/server.js";
|
|
37
|
+
import { attachTerminal } from "./web/terminal.js";
|
|
37
38
|
const log = logger("pier");
|
|
38
39
|
// Pier owns the Pi runtime dir. Set before any SDK call resolves a path, so
|
|
39
40
|
// everything Pi derives from its agent dir (auth.json, models.json, sessions,
|
|
@@ -280,6 +281,9 @@ const server = serve({ fetch: app.fetch, port, hostname }, () => {
|
|
|
280
281
|
log.info(`workbench on http://${hostname}:${port}`);
|
|
281
282
|
log.info(`pid ${process.pid}, node ${process.version}, home ${PIER_HOME}`);
|
|
282
283
|
});
|
|
284
|
+
// The one WebSocket surface (see web/terminal.ts); `serve` above builds a
|
|
285
|
+
// plain node:http server, which is the only shape with an upgrade event.
|
|
286
|
+
const terminals = attachTerminal(server, auth);
|
|
283
287
|
// A crash and a clean stop must be distinguishable after the fact, and both
|
|
284
288
|
// left nothing behind before this.
|
|
285
289
|
process.on("uncaughtException", (err) => {
|
|
@@ -302,6 +306,7 @@ const shutdown = (stopTasks = true) => {
|
|
|
302
306
|
// `systemctl restart` into a 90-second wait for SIGKILL.
|
|
303
307
|
setTimeout(() => process.exit(0), 3000).unref();
|
|
304
308
|
stopEviction();
|
|
309
|
+
terminals.close(); // no shell outlives the workbench
|
|
305
310
|
// The drain path leaves task runs alone: aborting them here would record
|
|
306
311
|
// them cancelled and race their callbacks against dying channels, when the
|
|
307
312
|
// boot-time interrupted marking is the recovery that was promised.
|
package/dist/web/auth.js
CHANGED
|
@@ -59,6 +59,7 @@ export class AuthStore {
|
|
|
59
59
|
#db;
|
|
60
60
|
/** HMAC key for cookies: the hash, so rotating the password expires them. */
|
|
61
61
|
#key;
|
|
62
|
+
#rotationListeners = new Set();
|
|
62
63
|
constructor(db = pierDb(), print = (m) => log.info(m)) {
|
|
63
64
|
this.#db = db;
|
|
64
65
|
let row = this.#row();
|
|
@@ -99,6 +100,13 @@ export class AuthStore {
|
|
|
99
100
|
.prepare("UPDATE auth SET salt = ?, hash = ?, created_at = ? WHERE id = 1")
|
|
100
101
|
.run(salt, next, Date.now());
|
|
101
102
|
this.#key = next;
|
|
103
|
+
for (const listener of this.#rotationListeners)
|
|
104
|
+
listener();
|
|
105
|
+
}
|
|
106
|
+
/** A long-lived authenticated surface closes itself when every cookie is
|
|
107
|
+
* revoked. The store and listeners share the process lifetime. */
|
|
108
|
+
onRotation(listener) {
|
|
109
|
+
this.#rotationListeners.add(listener);
|
|
102
110
|
}
|
|
103
111
|
/** Cookie signing key. Never the password: that is not stored anywhere. */
|
|
104
112
|
get cookieKey() {
|
|
@@ -181,19 +189,14 @@ function noteFailure(client) {
|
|
|
181
189
|
failures.set(client, { count: 1, resetAt: Date.now() + WINDOW_MS });
|
|
182
190
|
}
|
|
183
191
|
/** Browsers name the source of unsafe requests. Compare hosts rather than
|
|
184
|
-
* schemes because TLS commonly terminates at the reverse proxy.
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
* schemes because TLS commonly terminates at the reverse proxy. Shared by HTTP
|
|
193
|
+
* and WebSocket so the password boundary cannot disagree with itself. */
|
|
194
|
+
function originMatches(origin, host) {
|
|
187
195
|
if (!origin)
|
|
188
196
|
return true; // curl and other non-browser clients
|
|
189
197
|
try {
|
|
190
198
|
const parsed = new URL(origin);
|
|
191
|
-
const
|
|
192
|
-
const forwarded = remote && loopback(remote)
|
|
193
|
-
? c.req.header("x-forwarded-host")?.split(",").at(-1)?.trim()
|
|
194
|
-
: undefined;
|
|
195
|
-
const host = forwarded || c.req.header("host") || new URL(c.req.url).host;
|
|
196
|
-
const external = new URL(`${parsed.protocol}//${host}`);
|
|
199
|
+
const external = new URL(`${parsed.protocol}//${host ?? ""}`);
|
|
197
200
|
return parsed.origin === origin && external.origin === parsed.origin &&
|
|
198
201
|
external.pathname === "/" && !external.search && !external.hash;
|
|
199
202
|
}
|
|
@@ -201,6 +204,30 @@ function sameOrigin(c) {
|
|
|
201
204
|
return false;
|
|
202
205
|
}
|
|
203
206
|
}
|
|
207
|
+
function sameOrigin(c) {
|
|
208
|
+
const remote = remoteOf(c);
|
|
209
|
+
const forwarded = remote && loopback(remote)
|
|
210
|
+
? c.req.header("x-forwarded-host")?.split(",").at(-1)?.trim()
|
|
211
|
+
: undefined;
|
|
212
|
+
return originMatches(c.req.header("origin"), forwarded || c.req.header("host") || new URL(c.req.url).host);
|
|
213
|
+
}
|
|
214
|
+
/** The same cookie + Origin boundary for a WebSocket upgrade, where no Hono
|
|
215
|
+
* context exists before the handshake completes. */
|
|
216
|
+
export function upgradeAuthorized(store, req) {
|
|
217
|
+
const raw = req.headers.cookie
|
|
218
|
+
?.split(";")
|
|
219
|
+
.map((part) => part.trim())
|
|
220
|
+
.find((part) => part.startsWith(`${COOKIE}=`))
|
|
221
|
+
?.slice(COOKIE.length + 1);
|
|
222
|
+
if (!valid(store.cookieKey, raw))
|
|
223
|
+
return false;
|
|
224
|
+
const remote = req.socket.remoteAddress ?? "";
|
|
225
|
+
const forwardedHeader = req.headers["x-forwarded-host"];
|
|
226
|
+
const forwarded = loopback(remote) && typeof forwardedHeader === "string"
|
|
227
|
+
? forwardedHeader.split(",").at(-1)?.trim()
|
|
228
|
+
: undefined;
|
|
229
|
+
return originMatches(req.headers.origin, forwarded || req.headers.host);
|
|
230
|
+
}
|
|
204
231
|
/** Every route, in one place — no per-route opt-in to forget on the next one. */
|
|
205
232
|
export function requireAuth(store) {
|
|
206
233
|
return async (c, next) => {
|
|
File without changes
|