@timqi/pier 0.1.3 → 0.1.5

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 (136) hide show
  1. package/README.md +12 -7
  2. package/dist/agent/config.js +9 -3
  3. package/dist/agent/events.js +13 -5
  4. package/dist/agent/listing.js +10 -2
  5. package/dist/agent/packages.js +4 -20
  6. package/dist/agent/pi.js +18 -86
  7. package/dist/channels/attach.js +2 -2
  8. package/dist/channels/commands.js +10 -8
  9. package/dist/channels/config.js +32 -24
  10. package/dist/channels/control.js +60 -14
  11. package/dist/channels/conversations.js +60 -13
  12. package/dist/channels/handoff.js +90 -0
  13. package/dist/channels/lark-api.js +7 -0
  14. package/dist/channels/lark-outbound.js +19 -0
  15. package/dist/channels/lark-panel.js +41 -26
  16. package/dist/channels/lark-render.js +2 -1
  17. package/dist/channels/lark.js +35 -26
  18. package/dist/channels/lines.js +1 -1
  19. package/dist/channels/panel.js +324 -115
  20. package/dist/channels/receipts.js +25 -12
  21. package/dist/channels/routes.js +21 -7
  22. package/dist/channels/runtime.js +21 -7
  23. package/dist/channels/slack-api.js +19 -10
  24. package/dist/channels/slack-cli.js +503 -0
  25. package/dist/channels/slack-directory.js +2 -2
  26. package/dist/channels/slack-outbound.js +11 -1
  27. package/dist/channels/slack-panel.js +81 -40
  28. package/dist/channels/slack-render.js +3 -1
  29. package/dist/channels/slack-thread.js +41 -0
  30. package/dist/channels/slack-transcript.js +107 -0
  31. package/dist/channels/slack.js +38 -31
  32. package/dist/channels/types.js +1 -3
  33. package/dist/cli.js +166 -7
  34. package/dist/core/identity.js +61 -17
  35. package/dist/core/reply.js +19 -15
  36. package/dist/core/router.js +23 -3
  37. package/dist/db.js +33 -0
  38. package/dist/main.js +61 -41
  39. package/dist/paths.js +3 -0
  40. package/dist/secrets.js +2 -1
  41. package/dist/settings.js +3 -12
  42. package/dist/socket.js +99 -0
  43. package/dist/tasks/agent.js +5 -6
  44. package/dist/tasks/callbacks.js +2 -2
  45. package/dist/tasks/cli.js +225 -0
  46. package/dist/tasks/definitions.js +3 -3
  47. package/dist/tasks/execution.js +1 -3
  48. package/dist/tasks/groups.js +2 -5
  49. package/dist/tasks/messages.js +18 -158
  50. package/dist/tasks/operations.js +335 -0
  51. package/dist/tasks/routes.js +1 -11
  52. package/dist/tasks/runs.js +5 -20
  53. package/dist/tasks/service.js +18 -43
  54. package/dist/tasks/store.js +15 -15
  55. package/dist/tools.js +18 -3
  56. package/dist/vault.js +107 -0
  57. package/dist/web/auth.js +2 -1
  58. package/dist/web/public/assets/{activity-BSMeRcN2.js → activity-CrybM-E8.js} +2 -2
  59. package/dist/web/public/assets/activity-CrybM-E8.js.br +0 -0
  60. package/dist/web/public/assets/activity-CrybM-E8.js.gz +0 -0
  61. package/dist/web/public/assets/{boards-BCWQMZry.js → boards-Cw7_6J6L.js} +1 -1
  62. package/dist/web/public/assets/boards-Cw7_6J6L.js.br +0 -0
  63. package/dist/web/public/assets/boards-Cw7_6J6L.js.gz +0 -0
  64. package/dist/web/public/assets/{explorer-Cr4XTi4j.js → explorer-DV066dUD.js} +1 -1
  65. package/dist/web/public/assets/explorer-DV066dUD.js.br +0 -0
  66. package/dist/web/public/assets/explorer-DV066dUD.js.gz +0 -0
  67. package/dist/web/public/assets/index-BrNHu2qj.js +85 -0
  68. package/dist/web/public/assets/index-BrNHu2qj.js.br +0 -0
  69. package/dist/web/public/assets/index-BrNHu2qj.js.gz +0 -0
  70. package/dist/web/public/assets/index-DLszkDUV.css +2 -0
  71. package/dist/web/public/assets/index-DLszkDUV.css.br +0 -0
  72. package/dist/web/public/assets/index-DLszkDUV.css.gz +0 -0
  73. package/dist/web/public/assets/{runs-DdERzeac.js → runs-DDagTNaM.js} +1 -1
  74. package/dist/web/public/assets/runs-DDagTNaM.js.br +0 -0
  75. package/dist/web/public/assets/runs-DDagTNaM.js.gz +0 -0
  76. package/dist/web/public/assets/settings-BEdSeXpm.js +5 -0
  77. package/dist/web/public/assets/settings-BEdSeXpm.js.br +0 -0
  78. package/dist/web/public/assets/settings-BEdSeXpm.js.gz +0 -0
  79. package/dist/web/public/assets/{task-runs-C-dGUDsH.js → task-runs-BnMack9t.js} +1 -1
  80. package/dist/web/public/assets/task-runs-BnMack9t.js.br +0 -0
  81. package/dist/web/public/assets/task-runs-BnMack9t.js.gz +0 -0
  82. package/dist/web/public/assets/{tasks-CY30H1u1.js → tasks-BkW7YShZ.js} +1 -1
  83. package/dist/web/public/assets/tasks-BkW7YShZ.js.br +0 -0
  84. package/dist/web/public/assets/tasks-BkW7YShZ.js.gz +0 -0
  85. package/dist/web/public/index.html +2 -2
  86. package/dist/web/public/index.html.br +0 -0
  87. package/dist/web/public/index.html.gz +0 -0
  88. package/dist/web/push.js +4 -10
  89. package/dist/web/vault.js +68 -0
  90. package/dist/{extensions/web → websearch}/artifacts.js +2 -2
  91. package/dist/websearch/cli.js +73 -0
  92. package/dist/websearch/run.js +273 -0
  93. package/docs/deploy.md +34 -11
  94. package/package.json +2 -3
  95. package/skills/pier-help/SKILL.md +32 -16
  96. package/skills/pier-slack/SKILL.md +52 -71
  97. package/skills/pier-tasks/SKILL.md +60 -148
  98. package/skills/pier-vault/SKILL.md +36 -0
  99. package/skills/pier-web/SKILL.md +50 -0
  100. package/dist/channels/slack-tool.js +0 -416
  101. package/dist/channels/telegram-api.js +0 -86
  102. package/dist/channels/telegram-panel.js +0 -97
  103. package/dist/channels/telegram-render.js +0 -69
  104. package/dist/channels/telegram.js +0 -421
  105. package/dist/extensions/index.js +0 -10
  106. package/dist/extensions/web/index.js +0 -9
  107. package/dist/extensions/web/tools.js +0 -265
  108. package/dist/tasks/tool.js +0 -416
  109. package/dist/web/public/assets/activity-BSMeRcN2.js.br +0 -0
  110. package/dist/web/public/assets/activity-BSMeRcN2.js.gz +0 -0
  111. package/dist/web/public/assets/boards-BCWQMZry.js.br +0 -0
  112. package/dist/web/public/assets/boards-BCWQMZry.js.gz +0 -0
  113. package/dist/web/public/assets/explorer-Cr4XTi4j.js.br +0 -0
  114. package/dist/web/public/assets/explorer-Cr4XTi4j.js.gz +0 -0
  115. package/dist/web/public/assets/index-C7tA0Ufu.js +0 -85
  116. package/dist/web/public/assets/index-C7tA0Ufu.js.br +0 -0
  117. package/dist/web/public/assets/index-C7tA0Ufu.js.gz +0 -0
  118. package/dist/web/public/assets/index-DVIt5Gio.css +0 -2
  119. package/dist/web/public/assets/index-DVIt5Gio.css.br +0 -0
  120. package/dist/web/public/assets/index-DVIt5Gio.css.gz +0 -0
  121. package/dist/web/public/assets/runs-DdERzeac.js.br +0 -0
  122. package/dist/web/public/assets/runs-DdERzeac.js.gz +0 -0
  123. package/dist/web/public/assets/settings-CQDAHoMM.js +0 -5
  124. package/dist/web/public/assets/settings-CQDAHoMM.js.br +0 -0
  125. package/dist/web/public/assets/settings-CQDAHoMM.js.gz +0 -0
  126. package/dist/web/public/assets/task-runs-C-dGUDsH.js.br +0 -0
  127. package/dist/web/public/assets/task-runs-C-dGUDsH.js.gz +0 -0
  128. package/dist/web/public/assets/tasks-CY30H1u1.js.br +0 -0
  129. package/dist/web/public/assets/tasks-CY30H1u1.js.gz +0 -0
  130. /package/dist/{extensions/web → websearch}/anthropic.js +0 -0
  131. /package/dist/{extensions/web → websearch}/content.js +0 -0
  132. /package/dist/{extensions/web → websearch}/http.js +0 -0
  133. /package/dist/{extensions/web → websearch}/json.js +0 -0
  134. /package/dist/{extensions/web → websearch}/language.js +0 -0
  135. /package/dist/{extensions/web → websearch}/openai.js +0 -0
  136. /package/dist/{extensions/web → websearch}/provider.js +0 -0
@@ -1,5 +1,7 @@
1
- // Settings → Channels HTTP surface: one document per platform.
1
+ // Settings → Channels HTTP surface: one document per platform, and the
2
+ // web → IM handoff beside it.
2
3
  import { isThinkingLevel } from "../core/types.js";
4
+ import { HandoffError } from "./handoff.js";
3
5
  import { defaultChannelConfig, isChannelPlatform, } from "./types.js";
4
6
  /** Stable mask: recomputable at write time, so "unchanged" is detectable. */
5
7
  const maskToken = (token) => token ? `${"•".repeat(8)}${token.slice(-4)}` : "";
@@ -35,14 +37,13 @@ function parseChats(raw, known) {
35
37
  enabled: asBool(edit.enabled),
36
38
  requireMention: asBool(edit.requireMention),
37
39
  requireBind: asBool(edit.requireBind),
38
- topicMode: asBool(edit.topicMode),
39
40
  cwd: asString(edit.cwd),
40
41
  model: asModel(edit.model),
41
42
  thinking: asThinking(edit.thinking),
42
43
  };
43
44
  });
44
45
  }
45
- export function registerChannelRoutes(app, store, runtime) {
46
+ export function registerChannelRoutes(app, store, runtime, handoff) {
46
47
  app.get("/api/channels/:platform", (c) => {
47
48
  const platform = c.req.param("platform");
48
49
  if (!isChannelPlatform(platform))
@@ -70,12 +71,8 @@ export function registerChannelRoutes(app, store, runtime) {
70
71
  enabled: asBool(body.enabled),
71
72
  token: kept(asString(body.token), current.token),
72
73
  appToken: kept(asString(body.appToken), current.appToken),
73
- // Absent means "on": a client that predates the field must not silently
74
- // switch off a capability the operator never touched.
75
- agentTool: body.agentTool === undefined ? current.agentTool : asBool(body.agentTool),
76
74
  requireMention: asBool(body.requireMention),
77
75
  requireBind: asBool(body.requireBind),
78
- topicMode: asBool(body.topicMode),
79
76
  cwd: asString(body.cwd),
80
77
  model: asModel(body.model),
81
78
  thinking: asThinking(body.thinking),
@@ -100,4 +97,21 @@ export function registerChannelRoutes(app, store, runtime) {
100
97
  store.unbind(platform, c.req.param("id"));
101
98
  return c.json({ ok: true });
102
99
  });
100
+ app.get("/api/handoff/targets", (c) => c.json({ targets: handoff.targets() }));
101
+ app.post("/api/handoff", async (c) => {
102
+ const body = (await c.req.json().catch(() => null));
103
+ const sessionId = asString(body?.sessionId);
104
+ const chatId = asString(body?.chatId);
105
+ const platform = body?.platform;
106
+ if (!sessionId || !chatId || !isChannelPlatform(platform))
107
+ return c.json({ error: "invalid body" }, 400);
108
+ try {
109
+ return c.json(await handoff.continueIn({ sessionId, platform, chatId }), 201);
110
+ }
111
+ catch (err) {
112
+ if (err instanceof HandoffError)
113
+ return c.json({ error: err.message }, err.status);
114
+ throw err;
115
+ }
116
+ });
103
117
  }
@@ -3,12 +3,10 @@
3
3
  import { logger } from "../log.js";
4
4
  import { LarkChannel } from "./lark.js";
5
5
  import { SlackChannel } from "./slack.js";
6
- import { TelegramChannel } from "./telegram.js";
7
6
  const ADAPTERS = [
8
- { platform: "telegram", needsAppToken: false, build: (deps) => new TelegramChannel(deps) },
9
- { platform: "slack", needsAppToken: true, build: (deps) => new SlackChannel(deps) },
7
+ { platform: "slack", build: (deps) => new SlackChannel(deps) },
10
8
  // Lark's "token" is the App ID and "appToken" the App Secret.
11
- { platform: "lark", needsAppToken: true, build: (deps) => new LarkChannel(deps) },
9
+ { platform: "lark", build: (deps) => new LarkChannel(deps) },
12
10
  ];
13
11
  // The injected sink is for warnings; "slack started" is not one.
14
12
  const log = logger("channels");
@@ -18,14 +16,18 @@ export class ChannelRuntime {
18
16
  store;
19
17
  router;
20
18
  control;
19
+ handoff;
21
20
  log;
22
21
  live = new Map();
23
22
  reloading = Promise.resolve();
24
23
  stopped = false;
25
- constructor(store, router, control, log = warn) {
24
+ constructor(store, router, control,
25
+ /** The pull half only; the push half needs this runtime, so main.ts closes the loop. */
26
+ handoff, log = warn) {
26
27
  this.store = store;
27
28
  this.router = router;
28
29
  this.control = control;
30
+ this.handoff = handoff;
29
31
  this.log = log;
30
32
  }
31
33
  /** Serialized: two concurrent Console saves would race into duplicate live
@@ -45,7 +47,7 @@ export class ChannelRuntime {
45
47
  return run;
46
48
  }
47
49
  async restart(adapter) {
48
- const { platform, needsAppToken, build } = adapter;
50
+ const { platform, build } = adapter;
49
51
  const existing = this.live.get(platform);
50
52
  if (existing) {
51
53
  this.live.delete(platform);
@@ -55,7 +57,7 @@ export class ChannelRuntime {
55
57
  const config = this.store.get(platform);
56
58
  if (!config.enabled || !config.token)
57
59
  return;
58
- if (needsAppToken && !config.appToken) {
60
+ if (!config.appToken) {
59
61
  // "Enabled but nothing happens" is indistinguishable from a broken adapter.
60
62
  this.log(`${platform}: enabled but no app token, not starting`);
61
63
  return;
@@ -64,6 +66,7 @@ export class ChannelRuntime {
64
66
  store: this.store,
65
67
  log: (m) => this.log(`${platform}: ${m}`),
66
68
  control: this.control,
69
+ handoff: this.handoff,
67
70
  });
68
71
  try {
69
72
  await channel.start((msg) => {
@@ -87,6 +90,17 @@ export class ChannelRuntime {
87
90
  await channel.notify(conversationId, { text, origin: { kind: "error" } });
88
91
  return true;
89
92
  }
93
+ running() {
94
+ return [...this.live.keys()];
95
+ }
96
+ /** Throws by name when the platform is not running: the caller's answer is
97
+ * "enable it in Settings", not a silent no-op. */
98
+ async openThread(platform, chatId, note) {
99
+ const channel = this.live.get(platform);
100
+ if (!channel)
101
+ throw new Error(`${platform} is not running`);
102
+ return channel.openThread(chatId, note);
103
+ }
90
104
  async stop() {
91
105
  // An in-flight restart could otherwise register an adapter after `live`
92
106
  // was cleared — running, unstoppable.
@@ -21,18 +21,24 @@ export class SlackApi {
21
21
  appToken;
22
22
  log;
23
23
  openSocket;
24
+ retries;
24
25
  socketRunning = false;
25
26
  constructor(token, appToken, log = () => { },
26
27
  /** Injected in tests. */
27
- openSocket = (url) => new WebSocket(url)) {
28
+ openSocket = (url) => new WebSocket(url),
29
+ /** How many rate-limit waits one call sits through. The adapter takes one:
30
+ * a turn must not hang for minutes. A batch process (`pier slack`) takes more. */
31
+ retries = 1) {
28
32
  this.token = token;
29
33
  this.appToken = appToken;
30
34
  this.log = log;
31
35
  this.openSocket = openSocket;
36
+ this.retries = retries;
32
37
  }
33
38
  /** Slack accepts a JSON body only on write methods; a read method silently
34
39
  * ignores it and reports the parameter missing (`users.info` answers
35
- * `user_not_found`). So reads go form-encoded. */
40
+ * `user_not_found`). So reads go form-encoded. Public for the long tail of
41
+ * read methods `pier slack` needs and the adapter never calls. */
36
42
  async read(method, params) {
37
43
  const form = new URLSearchParams();
38
44
  for (const [key, value] of Object.entries(params)) {
@@ -41,7 +47,7 @@ export class SlackApi {
41
47
  }
42
48
  return this.call(method, form);
43
49
  }
44
- async call(method, payload, token = this.token, retry = true) {
50
+ async call(method, payload, token = this.token, tries = this.retries) {
45
51
  const form = payload instanceof URLSearchParams;
46
52
  const res = await fetch(`${BASE}/${method}`, {
47
53
  method: "POST",
@@ -54,16 +60,17 @@ export class SlackApi {
54
60
  body: form ? payload.toString() : JSON.stringify(payload),
55
61
  signal: AbortSignal.timeout(30_000),
56
62
  });
63
+ const body = (await res.json().catch(() => null));
57
64
  // A long turn split into chunks hits ~1 msg/s per channel; the header
58
- // carries the exact wait. Obeyed once; a second 429 throws.
59
- if (res.status === 429 && retry) {
65
+ // carries the exact wait. Some methods say `ratelimited` in a 200 body
66
+ // instead. Obeyed `retries` times; the next one throws.
67
+ if ((res.status === 429 || body?.error === "ratelimited") && tries > 0) {
60
68
  const after = Number(res.headers.get("retry-after") ?? "1");
61
69
  if (Number.isFinite(after) && after <= 60) {
62
70
  await new Promise((r) => setTimeout(r, (after + 1) * 1000));
63
- return this.call(method, payload, token, false);
71
+ return this.call(method, payload, token, tries - 1);
64
72
  }
65
73
  }
66
- const body = (await res.json().catch(() => null));
67
74
  if (!body)
68
75
  throw new Error(`slack ${method}: ${res.status} with no JSON body`);
69
76
  if (!body.ok)
@@ -259,7 +266,7 @@ export class SlackApi {
259
266
  }
260
267
  /** Three calls is Slack's current upload (`files.upload` is retired); the
261
268
  * upload host is not the Web API and answers plain text. */
262
- async uploadFile(channel, threadTs, file) {
269
+ async uploadFile(channel, threadTs, file, comment) {
263
270
  const slot = await this.read("files.getUploadURLExternal", { filename: file.name, length: file.bytes.length }).catch((err) => {
264
271
  // The manifest is only applied when an app is created; name the fix.
265
272
  if (!/missing_scope/.test(String(err)))
@@ -279,10 +286,12 @@ export class SlackApi {
279
286
  });
280
287
  if (!put.ok)
281
288
  throw new Error(`slack file upload: ${put.status}`);
282
- await this.call("files.completeUploadExternal", {
289
+ const done = await this.call("files.completeUploadExternal", {
283
290
  files: [{ id: slot.file_id, title: file.name }],
284
291
  channel_id: channel,
285
- thread_ts: threadTs,
292
+ thread_ts: threadTs || undefined,
293
+ initial_comment: comment,
286
294
  });
295
+ return { id: done.files?.[0]?.id ?? slot.file_id };
287
296
  }
288
297
  }