@plannotator/pi-extension 0.28.48 → 0.28.49
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/generated/config.ts +117 -17
- package/generated/guide-viewer-manifest.ts +2 -2
- package/generated/inbox/connection.ts +54 -1
- package/generated/inbox/project-key.ts +10 -0
- package/generated/shape/core.ts +1070 -0
- package/generated/shape/format.ts +5 -0
- package/generated/shape/routes.ts +283 -0
- package/generated/shape/store.ts +687 -0
- package/generated/tailnet-audience.ts +13 -6
- package/generated/ulid.ts +21 -0
- package/package.json +1 -1
- package/plannotator.html +431 -425
- package/release.json +1 -1
- package/review-editor.html +406 -400
- package/server/code-nav-runtime.ts +47 -0
- package/server/serverReview.ts +2 -42
- package/server/serverShape.test.ts +147 -0
- package/server/serverShape.ts +118 -0
- package/skills/plannotator/SKILL.md +9 -0
package/generated/config.ts
CHANGED
|
@@ -331,12 +331,12 @@ export interface PlannotatorConfig {
|
|
|
331
331
|
/**
|
|
332
332
|
* The `plannotator` agent tool (packages/shared/plannotator-tool.ts) on the
|
|
333
333
|
* hosts that register it: the Claude Code mod, Pi (and the Pi extension in
|
|
334
|
-
* Oh My Pi) and
|
|
335
|
-
* keeps it out (the slash commands are unchanged either
|
|
336
|
-
* a session starts, so a change applies to the next
|
|
337
|
-
* PLANNOTATOR_AGENT_TOOL wins over this key. Unset: the host's
|
|
338
|
-
* AGENT_TOOL_DEFAULTS (on for the Claude Code mod and Oh My Pi, off
|
|
339
|
-
*
|
|
334
|
+
* Oh My Pi), OpenCode 2 and DeepSeek Harness. `true` puts it in the agent's
|
|
335
|
+
* tool list, `false` keeps it out (the slash commands are unchanged either
|
|
336
|
+
* way). Read once when a session starts, so a change applies to the next
|
|
337
|
+
* session. PLANNOTATOR_AGENT_TOOL wins over this key. Unset: the host's
|
|
338
|
+
* default, AGENT_TOOL_DEFAULTS (on for the Claude Code mod and Oh My Pi, off
|
|
339
|
+
* on Pi, OpenCode 2 and DeepSeek Harness).
|
|
340
340
|
*/
|
|
341
341
|
agentTool?: boolean;
|
|
342
342
|
/**
|
|
@@ -425,7 +425,8 @@ export interface PlannotatorConfig {
|
|
|
425
425
|
experimental?: { decisionMode?: boolean };
|
|
426
426
|
/**
|
|
427
427
|
* The Board's one-time first-visit dialog (approval "inbox-board", intro
|
|
428
|
-
* amendment): `shownAt` once a page on this computer
|
|
428
|
+
* amendment): `shownAt` once a page on this computer or the owner's phone
|
|
429
|
+
* claimed it (or the Board reveal showed it)
|
|
429
430
|
* (`POST /api/inbox/board/intro`, a compare-and-set), once per machine.
|
|
430
431
|
*/
|
|
431
432
|
inboxBoardIntro?: { shownAt?: string };
|
|
@@ -453,12 +454,61 @@ export interface PlannotatorConfig {
|
|
|
453
454
|
inboxSnoozeIntro?: { firstUseAt?: string; snoozedAt?: string; shownAt?: string };
|
|
454
455
|
/**
|
|
455
456
|
* The Board (approval "inbox-board", 2026-10-26 amendment): something the
|
|
456
|
-
* person turns on
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
457
|
+
* person turns on. On: the sidebar's Board row and page, and the Inbox
|
|
458
|
+
* serves the agents' `card` tool. Off: neither, every card kept. Written by
|
|
459
|
+
* the Inbox's Settings → Capabilities (`POST /api/inbox/settings`
|
|
460
|
+
* `{ board: { on } }`), read on every request. Three readings (approval
|
|
461
|
+
* "inbox-feature-reveal"): unset = not decided yet (off, and the only state
|
|
462
|
+
* the reveal can turn on), `true` = on, `false` = the person switched it
|
|
463
|
+
* off. The reveal's claim is the only automatic write, unset -> `true`.
|
|
460
464
|
*/
|
|
461
465
|
inboxBoard?: boolean;
|
|
466
|
+
/**
|
|
467
|
+
* Thread steps (approval "inbox-thread-steps", 2026-11-03): something the
|
|
468
|
+
* person turns on, off by default. On: the thread's steps button, the
|
|
469
|
+
* steps in the Board's opened card, and the agents' `steps` tool, wake
|
|
470
|
+
* line, compaction line and Tidy rule. Off: none of it, every list kept.
|
|
471
|
+
* Written by the Inbox's Settings → Capabilities (`POST
|
|
472
|
+
* /api/inbox/settings` `{ steps: { on } }`), read on every request.
|
|
473
|
+
*/
|
|
474
|
+
inboxSteps?: boolean;
|
|
475
|
+
/**
|
|
476
|
+
* Code review in the Inbox (approval "inbox-code-review-direction",
|
|
477
|
+
* 2026-11-03): something the person turns on, off by default. On: the
|
|
478
|
+
* review skills in the reply box's "/" list, the review card and its
|
|
479
|
+
* full-window review with Approve / Send feedback, and submit_guide's
|
|
480
|
+
* `review` field. Off: none of it; guided reviews and `plannotator review`
|
|
481
|
+
* stay as they are. Three readings, like the Board's: unset = not decided
|
|
482
|
+
* yet (off), `true` = on, `false` = the person switched it off. Written by
|
|
483
|
+
* the Inbox's Settings → Capabilities (`POST /api/inbox/settings`
|
|
484
|
+
* `{ code_review: { on } }`), read on every request.
|
|
485
|
+
*/
|
|
486
|
+
inboxCodeReview?: boolean;
|
|
487
|
+
/**
|
|
488
|
+
* Shape in the Inbox (approval "shape-inbox-placement"): living boards an
|
|
489
|
+
* agent draws and keeps current. Off unless the person turns it on. On: the
|
|
490
|
+
* thread header's Pebble, the sidebar's Shape row (3+ boards), the Shape
|
|
491
|
+
* group in the search, the `/api/shapes` routes and the shape skill in the
|
|
492
|
+
* reply box's "/" list. Off: none of it, and no agent is told of it. Three
|
|
493
|
+
* readings, like the Board's: unset (off), `true`, `false`. Written by
|
|
494
|
+
* Settings → Capabilities (`POST /api/inbox/settings` `{ shape: { on } }`).
|
|
495
|
+
*/
|
|
496
|
+
inboxShape?: boolean;
|
|
497
|
+
/**
|
|
498
|
+
* The queue of one-time reveals for returning users (approval
|
|
499
|
+
* "inbox-feature-reveal"; rules in packages/shared/inbox/reveals.ts). Written
|
|
500
|
+
* only by the server. `lastShownAt`: the last one-time announcement claimed
|
|
501
|
+
* on this machine (list options, voice, Snooze, the Board), the 48 h
|
|
502
|
+
* spacing's clock. `waitingSince`: when each reveal first became next, for
|
|
503
|
+
* the 3-day patience. `board.shownAt`: the Board reveal was claimed.
|
|
504
|
+
* `steps.shownAt`: the Checklist reveal was claimed.
|
|
505
|
+
*/
|
|
506
|
+
inboxReveals?: {
|
|
507
|
+
lastShownAt?: string;
|
|
508
|
+
waitingSince?: { snooze?: string; board?: string; steps?: string };
|
|
509
|
+
board?: { shownAt?: string };
|
|
510
|
+
steps?: { shownAt?: string };
|
|
511
|
+
};
|
|
462
512
|
inboxSound?: {
|
|
463
513
|
enabled?: boolean;
|
|
464
514
|
volume?: number;
|
|
@@ -613,6 +663,14 @@ export interface PlannotatorConfig {
|
|
|
613
663
|
* preference cookies, #2184); remote sessions ignore those names.
|
|
614
664
|
*/
|
|
615
665
|
urlHost?: string;
|
|
666
|
+
/**
|
|
667
|
+
* Publish every `plannotator review`, `annotate` and `annotate-last`/`last`
|
|
668
|
+
* session over the tailnet, as if `--tailscale` were passed, so a host or
|
|
669
|
+
* agent need not add the flag each time. PLANNOTATOR_TAILSCALE wins over
|
|
670
|
+
* this key; the flag turns it on for one run. Other subcommands ignore it.
|
|
671
|
+
* The Inbox has its own key (`inboxTailscale`). Default: false.
|
|
672
|
+
*/
|
|
673
|
+
tailscale?: boolean;
|
|
616
674
|
/**
|
|
617
675
|
* Mirror the approved plan checklist into an editable todo provider during
|
|
618
676
|
* execution (issue #484). "auto" (default) syncs whenever a provider is
|
|
@@ -1360,7 +1418,7 @@ export function resolveClaudeCodeMod(
|
|
|
1360
1418
|
* from plain Pi itself (`apps/pi-extension/pi-host.ts`): it is the same
|
|
1361
1419
|
* extension and the same session bridge (host `pi`), with its own default.
|
|
1362
1420
|
*/
|
|
1363
|
-
export type AgentToolHost = "claude-code" | "pi" | "oh-my-pi" | "opencode";
|
|
1421
|
+
export type AgentToolHost = "claude-code" | "pi" | "oh-my-pi" | "opencode" | "dsh";
|
|
1364
1422
|
|
|
1365
1423
|
/**
|
|
1366
1424
|
* Whether each host registers the `plannotator` agent tool when nothing is
|
|
@@ -1368,8 +1426,10 @@ export type AgentToolHost = "claude-code" | "pi" | "oh-my-pi" | "opencode";
|
|
|
1368
1426
|
* mod (the tool is deferred behind tool search there, so it costs only its
|
|
1369
1427
|
* name until used) and for Oh My Pi (owner's call 2026-10-12: OMP lists an
|
|
1370
1428
|
* extension tool as one line in its system prompt, about 50 tokens, and loads
|
|
1371
|
-
* the schema only when the model reads it); off on Pi
|
|
1372
|
-
*
|
|
1429
|
+
* the schema only when the model reads it); off on Pi, OpenCode 2 and
|
|
1430
|
+
* DeepSeek Harness (`dsh`, the `@plannotator/dsh` plugin; owner's call
|
|
1431
|
+
* 2026-11-02): a full tool definition, about 800 tokens, in every request,
|
|
1432
|
+
* and DSH keeps its tool list fixed for request caching. The mod's mirror
|
|
1373
1433
|
* (`apps/hook/hooks/mod/enabled.ts`) is kept equal to the claude-code entry by
|
|
1374
1434
|
* `enabled.test.ts`.
|
|
1375
1435
|
*/
|
|
@@ -1378,6 +1438,7 @@ export const AGENT_TOOL_DEFAULTS: Readonly<Record<AgentToolHost, boolean>> = {
|
|
|
1378
1438
|
pi: false,
|
|
1379
1439
|
"oh-my-pi": true,
|
|
1380
1440
|
opencode: false,
|
|
1441
|
+
dsh: false,
|
|
1381
1442
|
};
|
|
1382
1443
|
|
|
1383
1444
|
/**
|
|
@@ -1436,14 +1497,13 @@ export type InboxToolHost = "claude-code" | "pi" | "opencode" | "dsh";
|
|
|
1436
1497
|
* nothing is set: the agent tool's rule (on for the Claude Code mod, where
|
|
1437
1498
|
* tools are deferred behind tool search; off on Pi, OpenCode 2 and DeepSeek
|
|
1438
1499
|
* Harness, which send every tool's full definition with each request; DSH
|
|
1439
|
-
* also keeps its tool list fixed for request caching).
|
|
1440
|
-
* `plannotator` agent tool yet, so its entry is its own, off like Pi's.
|
|
1500
|
+
* also keeps its tool list fixed for request caching).
|
|
1441
1501
|
*/
|
|
1442
1502
|
export const INBOX_TOOL_DEFAULTS: Readonly<Record<InboxToolHost, boolean>> = {
|
|
1443
1503
|
"claude-code": AGENT_TOOL_DEFAULTS["claude-code"],
|
|
1444
1504
|
pi: AGENT_TOOL_DEFAULTS.pi,
|
|
1445
1505
|
opencode: AGENT_TOOL_DEFAULTS.opencode,
|
|
1446
|
-
dsh:
|
|
1506
|
+
dsh: AGENT_TOOL_DEFAULTS.dsh,
|
|
1447
1507
|
};
|
|
1448
1508
|
|
|
1449
1509
|
export const INBOX_TOOL_HOSTS: readonly InboxToolHost[] = ["claude-code", "pi", "opencode", "dsh"];
|
|
@@ -1664,6 +1724,46 @@ export function resolveInboxTailscale(
|
|
|
1664
1724
|
return { on: false, source: "default" };
|
|
1665
1725
|
}
|
|
1666
1726
|
|
|
1727
|
+
export type SessionTailscaleSource = "flag" | "env" | "config" | "default";
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* Resolve whether a review / annotate / annotate-last session is published
|
|
1731
|
+
* over the tailnet (the per-session `--tailscale` transport, not the Inbox's).
|
|
1732
|
+
*
|
|
1733
|
+
* Priority (highest wins):
|
|
1734
|
+
* `--tailscale` → PLANNOTATOR_TAILSCALE → config.tailscale → default false
|
|
1735
|
+
*
|
|
1736
|
+
* The flag only turns it on. The env var takes the same words as
|
|
1737
|
+
* PLANNOTATOR_INBOX_TAILSCALE: `1` / `true` / `on` turn it on, `0` / `false` /
|
|
1738
|
+
* `off` / `disabled` keep it off against the config key (a one-run opt-out);
|
|
1739
|
+
* an empty or unrecognized value counts as unset.
|
|
1740
|
+
*/
|
|
1741
|
+
export function resolveSessionTailscale(
|
|
1742
|
+
config: PlannotatorConfig,
|
|
1743
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1744
|
+
flag = false,
|
|
1745
|
+
): { on: boolean; source: SessionTailscaleSource } {
|
|
1746
|
+
if (flag) return { on: true, source: "flag" };
|
|
1747
|
+
const v = env.PLANNOTATOR_TAILSCALE?.trim().toLowerCase();
|
|
1748
|
+
if (v === "1" || v === "true" || v === "on") return { on: true, source: "env" };
|
|
1749
|
+
if (v === "0" || v === "false" || v === "off" || v === "disabled") return { on: false, source: "env" };
|
|
1750
|
+
const fromConfig = parseConfigBoolean(config.tailscale);
|
|
1751
|
+
if (fromConfig !== undefined) return { on: fromConfig, source: "config" };
|
|
1752
|
+
return { on: false, source: "default" };
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* How to run one session without the tailnet, worded for what turned it on:
|
|
1757
|
+
* dropping the flag does nothing when the env var or config key decided.
|
|
1758
|
+
*/
|
|
1759
|
+
export function sessionTailscaleOptOut(source: SessionTailscaleSource): string {
|
|
1760
|
+
return source === "flag"
|
|
1761
|
+
? "run the same command without --tailscale"
|
|
1762
|
+
: source === "env"
|
|
1763
|
+
? "run the same command with PLANNOTATOR_TAILSCALE unset or set to 0"
|
|
1764
|
+
: "run the same command with PLANNOTATOR_TAILSCALE=0 (config.json's \"tailscale\" key turned it on)";
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1667
1767
|
/**
|
|
1668
1768
|
* config.inboxTailscaleAllow, normalized: trimmed, lower-cased (logins are
|
|
1669
1769
|
* compared case-insensitively), deduplicated. Entries that are not strings,
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import type { GuideViewerAssets } from "./guide-format.ts";
|
|
7
7
|
|
|
8
8
|
export const GUIDE_VIEWER_MANIFEST: Omit<GuideViewerAssets, "baseUrl"> = {
|
|
9
|
-
js: "viewer.
|
|
9
|
+
js: "viewer.BZxaYwuG.js",
|
|
10
10
|
css: "viewer.BTKuQZGP.css",
|
|
11
|
-
jsIntegrity: "sha384-
|
|
11
|
+
jsIntegrity: "sha384-ePGbxJypqNlC5Dxx7heFv5OK1PvvxkzJYLOWnek0szk065nlTi816+1dQ47ndLPA",
|
|
12
12
|
cssIntegrity: "sha384-D0ugA4xJZl9NZisbmBfoi17BEAj8FqUmL/qNQa2ecghlvrmiMaiBIxlkeKGhRFgp",
|
|
13
13
|
langs: {
|
|
14
14
|
"astro": "chunks/astro.Ts5EKq2l.js",
|
|
@@ -49,6 +49,7 @@ export const INBOX_TOOL_ACTIONS = [
|
|
|
49
49
|
'submit_guide',
|
|
50
50
|
'get_guide_brief',
|
|
51
51
|
'card',
|
|
52
|
+
'steps',
|
|
52
53
|
] as const
|
|
53
54
|
|
|
54
55
|
export type InboxToolAction = (typeof INBOX_TOOL_ACTIONS)[number]
|
|
@@ -238,6 +239,9 @@ export function inboxSessionThreadsText(result: unknown, toolName: string = INBO
|
|
|
238
239
|
const open = typeof questions?.open === 'number' && questions.open > 0 ? questions.open : 0
|
|
239
240
|
if (open > 0) parts.push(`${open} open question${open === 1 ? '' : 's'}`)
|
|
240
241
|
if (thread!.unread === true) parts.push('the person wrote something you have not read')
|
|
242
|
+
// Your steps there (approval "inbox-thread-steps"): the Inbox lists them only while the person has steps on.
|
|
243
|
+
const steps = thread!.steps as { done?: unknown; total?: unknown } | undefined
|
|
244
|
+
if (typeof steps?.done === 'number' && typeof steps.total === 'number' && steps.total > 0) parts.push(`steps ${steps.done}/${steps.total}`)
|
|
241
245
|
const at = time(thread!.last_at)
|
|
242
246
|
if (at) parts.push(`last ${thread!.last_author === 'person' ? 'the person' : 'an agent'} ${at}`)
|
|
243
247
|
lines.push(parts.join(' · '))
|
|
@@ -388,6 +392,44 @@ export const INBOX_TOOL_HELP_NOTE = "Every field and question syntax: `plannotat
|
|
|
388
392
|
*/
|
|
389
393
|
export const INBOX_TOOL_BOARD_OFF_NOTE = "The Board (cards for work that outlasts a reply) is off until the person turns it on; see `plannotator card --help`."
|
|
390
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Thread steps (approval "inbox-thread-steps", 2026-11-03): the owner's
|
|
397
|
+
* approved text word for word, after `steps: `. It rides the `steps` field's
|
|
398
|
+
* description (a field description does not count toward Claude Code's 2,048
|
|
399
|
+
* characters; the owner chose this over a cut description) and is the MCP
|
|
400
|
+
* tool's description. Served only while the person has steps on (config.json
|
|
401
|
+
* `inboxSteps`): off, no agent text carries any of it.
|
|
402
|
+
*/
|
|
403
|
+
export const INBOX_STEPS_LEAD =
|
|
404
|
+
'your plan for the work in this thread, as the person should see it: 3 to 8 short steps, the whole list each call (it replaces the last one). status: todo, doing, done, waiting (on the person), dropped (say why in note). Call it when a step starts or ends; it sends nothing and notifies no one. Skip it for work that ends in this reply.'
|
|
405
|
+
|
|
406
|
+
/** The agent tool's line for the `steps` action (the owner, 2026-11-03): short, so the description stays under 2,048. */
|
|
407
|
+
export const INBOX_STEPS_ACTION_LINE = "keep the person's checklist for this thread current (details on the steps field)."
|
|
408
|
+
|
|
409
|
+
/** The `steps` field (the action's, and send_message's while steps are on): the approved text, then where a list goes before there is a thread. */
|
|
410
|
+
export const INBOX_STEPS_FIELD_DESCRIPTION = `steps: ${INBOX_STEPS_LEAD} No thread yet: send it with the send_message that starts one.`
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* The wake's one line on a reply in a thread where the session keeps steps
|
|
414
|
+
* (approval "inbox-thread-steps", approved text): "Your steps here: 3 of 6
|
|
415
|
+
* done; doing: X. Update them if that changed." Without a step in progress,
|
|
416
|
+
* the `doing` part is left out.
|
|
417
|
+
*/
|
|
418
|
+
export function inboxStepsWakeLine(steps: { done: number; total: number; doing: string | null }): string {
|
|
419
|
+
return `Your steps here: ${steps.done} of ${steps.total} done${steps.doing ? `; doing: ${steps.doing}` : ''}. Update them if that changed.`
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** A command's `steps` as the wake may print it, or null (absent, or not the Inbox's shape). */
|
|
423
|
+
function wakeSteps(value: unknown): { done: number; total: number; doing: string | null } | null {
|
|
424
|
+
if (!value || typeof value !== 'object') return null
|
|
425
|
+
const { done, total, doing } = value as Record<string, unknown>
|
|
426
|
+
const count = (n: unknown): n is number => typeof n === 'number' && Number.isInteger(n) && n >= 0 && n <= 12
|
|
427
|
+
if (!count(done) || !count(total) || total === 0 || done > total) return null
|
|
428
|
+
// One line, no control or separator characters: it goes into the session's prompt.
|
|
429
|
+
const step = typeof doing === 'string' ? doing.replace(/[\p{Cc}\p{Zl}\p{Zp}]+/gu, ' ').replace(/\s+/g, ' ').trim().slice(0, 80) : ''
|
|
430
|
+
return { done, total, doing: step || null }
|
|
431
|
+
}
|
|
432
|
+
|
|
391
433
|
/**
|
|
392
434
|
* The agent tool's own line for an action whose Inbox lead is long (the
|
|
393
435
|
* description must stay under Claude Code's 2,048 characters, and every Pi and
|
|
@@ -401,6 +443,7 @@ export const INBOX_AGENT_ACTION_LEADS: Readonly<Record<string, string>> = {
|
|
|
401
443
|
search: 'Find messages by words, older ones too (read-only).',
|
|
402
444
|
get_guide_brief: "How to write a guided review, with a worked submit_guide call.",
|
|
403
445
|
card: "A Board card for work that outlasts this reply: create it, then move it and say where it is as the work moves.",
|
|
446
|
+
steps: INBOX_STEPS_ACTION_LINE,
|
|
404
447
|
}
|
|
405
448
|
|
|
406
449
|
/**
|
|
@@ -927,6 +970,13 @@ export interface InboxReplyCommand {
|
|
|
927
970
|
* message and the body still says what it is. Absent on every other command.
|
|
928
971
|
*/
|
|
929
972
|
tidy?: boolean
|
|
973
|
+
/**
|
|
974
|
+
* The receiving session's steps in this thread (approval
|
|
975
|
+
* "inbox-thread-steps"), only while the person has steps on and the session
|
|
976
|
+
* keeps a list there: the wake adds `inboxStepsWakeLine`. Absent otherwise;
|
|
977
|
+
* an older connection ignores it.
|
|
978
|
+
*/
|
|
979
|
+
steps?: { done: number; total: number; doing: string | null }
|
|
930
980
|
}
|
|
931
981
|
|
|
932
982
|
/**
|
|
@@ -1234,6 +1284,7 @@ export function inboxWakeText(
|
|
|
1234
1284
|
reask_items?: unknown
|
|
1235
1285
|
reask_one?: unknown
|
|
1236
1286
|
tidy?: boolean
|
|
1287
|
+
steps?: unknown
|
|
1237
1288
|
},
|
|
1238
1289
|
toolName: string = INBOX_TOOL_NAME,
|
|
1239
1290
|
): string {
|
|
@@ -1246,7 +1297,9 @@ export function inboxWakeText(
|
|
|
1246
1297
|
const thread = wakeThreadId(command.thread_id)
|
|
1247
1298
|
return `Plannotator Inbox: ${ackSubject} (${command.id})\n${inboxAckLine(command.ack_of)}${thread ? `\n\n[thread ${thread}]` : ''}`
|
|
1248
1299
|
}
|
|
1249
|
-
|
|
1300
|
+
// The session's steps in this thread (approval "inbox-thread-steps"), its own paragraph before the closing line.
|
|
1301
|
+
const steps = wakeSteps(command.steps)
|
|
1302
|
+
const closing = `${steps ? `\n\n${inboxStepsWakeLine(steps)}` : ''}\n\n${inboxWakeClosingLine(command.thread_id, toolName)}`
|
|
1250
1303
|
const subject = (command.subject ?? '').replace(/\s+/g, ' ').trim() || (command.type === 'message' ? 'a message' : 'a reply')
|
|
1251
1304
|
// The thread was handed to another session: the notice alone.
|
|
1252
1305
|
if (command.handed_away && typeof command.handed_away === 'object') {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @generated — DO NOT EDIT. Source: packages/shared/inbox/project-key.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { sanitizeTag } from '../project.ts';
|
|
4
|
+
|
|
5
|
+
/** `<name>-<6 hex of the root>`, so two repositories with one name stay apart. */
|
|
6
|
+
export function inboxProjectKey(name: string, root: string, hashLength = 6): string {
|
|
7
|
+
const base = sanitizeTag(name) ?? "project";
|
|
8
|
+
const hash = createHash("sha256").update(root).digest("hex").slice(0, Math.min(hashLength, 64));
|
|
9
|
+
return `${base}-${hash}`;
|
|
10
|
+
}
|