@jmfederico/pi-web 1.202610.1 → 1.202610.2

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 (144) hide show
  1. package/README.md +23 -134
  2. package/dist/cli.js +4 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/index-DUsqiytv.js +4408 -0
  5. package/dist/client/assets/vendor-editor-core-C2PJX3uW.js +10 -0
  6. package/dist/client/assets/{vendor-editor-languages-DOCw3vc9.js → vendor-editor-languages-CS6n1J8n.js} +1 -1
  7. package/dist/client/index.html +4 -48
  8. package/dist/config.js +8 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/pi-packages/captains-log/dist/browser/index.js +30 -32
  11. package/dist/pi-packages/captains-log/package.json +4 -4
  12. package/dist/pi-packages/relays/extensions/index.js +60 -0
  13. package/dist/pi-packages/relays/package.json +10 -1
  14. package/dist/pi-packages/relays/pi-web-plugin.js +31 -1
  15. package/dist/pi-packages/relays/prompts/relay-worktree.md +6 -16
  16. package/dist/pi-packages/relays/prompts/relay.md +6 -33
  17. package/dist/pi-packages/relays/relayDispatch.js +67 -0
  18. package/dist/pi-packages/relays/relayIdentity.js +37 -0
  19. package/dist/pi-packages/relays/relaysPanelElement.js +37 -12
  20. package/dist/pi-packages/relays/skills/relay/SKILL.md +24 -68
  21. package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +40 -219
  22. package/dist/pi-packages/workflows/browser/pi-web-plugin.js +33 -0
  23. package/dist/pi-packages/workflows/package.json +22 -0
  24. package/dist/pi-packages/workflows/prompts/worktree.md +12 -0
  25. package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +49 -51
  26. package/dist/pi-web-plugins/git/browser/git-panel.js +0 -1
  27. package/dist/pi-web-plugins/git/server-plugin.js +11 -0
  28. package/dist/pi-web-plugins/git/workspace-discovery-notify.cjs +14 -0
  29. package/dist/pi-web-plugins/git/workspaceDiscoveryHooks.js +180 -0
  30. package/dist/pi-web-plugins/info/infoInternals.js +3 -3
  31. package/dist/pi-web-plugins/info/pi-web-plugin.js +1 -1
  32. package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +1077 -414
  33. package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +30 -30
  34. package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
  35. package/dist/piWebUpdate.js +2 -2
  36. package/dist/piWebUpdate.js.map +1 -1
  37. package/dist/plugin-api.d.ts +229 -8
  38. package/dist/server/activity/workspaceActivityService.js +4 -1
  39. package/dist/server/activity/workspaceActivityService.js.map +1 -1
  40. package/dist/server/app.js +5 -0
  41. package/dist/server/app.js.map +1 -1
  42. package/dist/server/configRoutes.js +6 -0
  43. package/dist/server/configRoutes.js.map +1 -1
  44. package/dist/server/knownAutoInstallPiPackages.js +6 -0
  45. package/dist/server/knownAutoInstallPiPackages.js.map +1 -1
  46. package/dist/server/navigationPreferenceRoutes.js +39 -0
  47. package/dist/server/navigationPreferenceRoutes.js.map +1 -0
  48. package/dist/server/plugins/projectPluginSettings.js +95 -0
  49. package/dist/server/plugins/projectPluginSettings.js.map +1 -0
  50. package/dist/server/plugins/serverPluginRuntime.js +26 -3
  51. package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
  52. package/dist/server/sessiond/autoInstallPiPackages.js +3 -3
  53. package/dist/server/sessiond/workspaceCatalogRoutes.js +3 -1
  54. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -1
  55. package/dist/server/sessiond/workspaceNotificationRoutes.js +41 -0
  56. package/dist/server/sessiond/workspaceNotificationRoutes.js.map +1 -0
  57. package/dist/server/sessiond.js +40 -7
  58. package/dist/server/sessiond.js.map +1 -1
  59. package/dist/server/sessions/askUserDomain.js +232 -0
  60. package/dist/server/sessions/askUserDomain.js.map +1 -0
  61. package/dist/server/sessions/askUserTool.js +15 -17
  62. package/dist/server/sessions/askUserTool.js.map +1 -1
  63. package/dist/server/sessions/dockerEnvironmentFacts.js +8 -0
  64. package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -1
  65. package/dist/server/sessions/extensionSessionDispatch.js +76 -0
  66. package/dist/server/sessions/extensionSessionDispatch.js.map +1 -0
  67. package/dist/server/sessions/globalProviderPolicy.js +71 -16
  68. package/dist/server/sessions/globalProviderPolicy.js.map +1 -1
  69. package/dist/server/sessions/messageHistoryTarget.js +61 -0
  70. package/dist/server/sessions/messageHistoryTarget.js.map +1 -0
  71. package/dist/server/sessions/piSessionManagerGateway.js +23 -24
  72. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  73. package/dist/server/sessions/piSessionService.js +581 -262
  74. package/dist/server/sessions/piSessionService.js.map +1 -1
  75. package/dist/server/sessions/sessionArchiveStore.js +5 -1
  76. package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
  77. package/dist/server/sessions/sessionArchiveTree.js +2 -2
  78. package/dist/server/sessions/sessionArchiveTree.js.map +1 -1
  79. package/dist/server/sessions/sessionCommandService.js +9 -3
  80. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  81. package/dist/server/sessions/sessionImageDimensions.js +31 -0
  82. package/dist/server/sessions/sessionImageDimensions.js.map +1 -0
  83. package/dist/server/sessions/sessionMediaIndex.js +7 -1
  84. package/dist/server/sessions/sessionMediaIndex.js.map +1 -1
  85. package/dist/server/sessions/sessionMetadata.js +51 -0
  86. package/dist/server/sessions/sessionMetadata.js.map +1 -0
  87. package/dist/server/sessions/sessionNameGenerator.js +8 -28
  88. package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
  89. package/dist/server/sessions/sessionRoutes.js +10 -2
  90. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  91. package/dist/server/sessions/sessionSummaryScanner.js +62 -5
  92. package/dist/server/sessions/sessionSummaryScanner.js.map +1 -1
  93. package/dist/server/sessions/spawnSessionTool.js +3 -1
  94. package/dist/server/sessions/spawnSessionTool.js.map +1 -1
  95. package/dist/server/sessions/spawnSubsessionTool.js +25 -17
  96. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  97. package/dist/server/sessions/subsessionCompletionDelivery.js +15 -0
  98. package/dist/server/sessions/subsessionCompletionDelivery.js.map +1 -0
  99. package/dist/server/sessions/subsessionCompletionInbox.js +134 -0
  100. package/dist/server/sessions/subsessionCompletionInbox.js.map +1 -0
  101. package/dist/server/sessions/subsessionProvenance.js +27 -0
  102. package/dist/server/sessions/subsessionProvenance.js.map +1 -0
  103. package/dist/server/status/machineStatusService.js +17 -5
  104. package/dist/server/status/machineStatusService.js.map +1 -1
  105. package/dist/server/storage/navigationPreferenceStore.js +137 -0
  106. package/dist/server/storage/navigationPreferenceStore.js.map +1 -0
  107. package/dist/server/workspaces/projectPiWebConfig.js +88 -6
  108. package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
  109. package/dist/server/workspaces/workspaceChangeNotifier.js +12 -0
  110. package/dist/server/workspaces/workspaceChangeNotifier.js.map +1 -0
  111. package/dist/server/workspaces/workspaceNotificationCommand.js +64 -0
  112. package/dist/server/workspaces/workspaceNotificationCommand.js.map +1 -0
  113. package/dist/server/workspaces/workspaceProviderRegistry.js +55 -0
  114. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
  115. package/dist/server-plugin-api.d.ts +18 -0
  116. package/dist/server-plugin-api.js.map +1 -1
  117. package/dist/sessiond/sessionDaemonClient.js +3 -3
  118. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  119. package/dist/shared/apiTypes.js +2 -2
  120. package/dist/shared/apiTypes.js.map +1 -1
  121. package/dist/shared/federatedRoutes.js +5 -0
  122. package/dist/shared/federatedRoutes.js.map +1 -1
  123. package/dist/shared/machineStatus.js +11 -2
  124. package/dist/shared/machineStatus.js.map +1 -1
  125. package/dist/shared/navigationPreferences.js +6 -0
  126. package/dist/shared/navigationPreferences.js.map +1 -0
  127. package/dist/shared/pluginApiTypes.d.ts +2 -0
  128. package/dist/shared/sessionMedia.js +6 -0
  129. package/dist/shared/sessionMedia.js.map +1 -1
  130. package/dist/shared/streamingBehavior.js +7 -0
  131. package/dist/shared/streamingBehavior.js.map +1 -0
  132. package/dist/shared/themePreference.js +23 -0
  133. package/dist/shared/themePreference.js.map +1 -0
  134. package/dist/workspaceNotificationCli.js +95 -0
  135. package/dist/workspaceNotificationCli.js.map +1 -0
  136. package/docs/config.md +99 -86
  137. package/docs/plugins.md +103 -30
  138. package/examples/session-bridge-plugin/package.json +2 -2
  139. package/examples/workspace-provider-plugin/package.json +1 -1
  140. package/package.json +33 -33
  141. package/dist/client/assets/index-CBjDnBIN.js +0 -4178
  142. package/dist/client/assets/vendor-editor-core-YWog6mAR.js +0 -10
  143. package/dist/server/sessions/pendingAskStore.js +0 -275
  144. package/dist/server/sessions/pendingAskStore.js.map +0 -1
@@ -1,44 +1,17 @@
1
1
  ---
2
- description: Establish a shared understanding, obtain approval, and dispatch a Relay
2
+ description: Clarify a goal with the human, then dispatch a Relay
3
3
  argument-hint: "<what the relay should achieve>"
4
4
  ---
5
5
 
6
- Prepare a Relay for the task source at the end of this prompt. The packet may be drafted immediately; dispatch still requires explicit human approval.
6
+ Prepare a Relay from the request below. Load the `relay` and `relay-runner` skills. Use the current checkout unless the human chooses another working location.
7
7
 
8
- If the task source is empty, ask what the Relay should achieve before doing anything else.
8
+ Help the human put into words what they want built, what counts as done, and the relay-specific boundaries, limitations, and conditions. Read supplied specifications and inspect the project only as needed to understand that intent. If the request is empty, ask what they want to achieve with `ask_user`.
9
9
 
10
- Select these skills before using their instructions:
10
+ Draft `.pi-web/relays/<name>/charter.md`, `status.md`, and `log.md` under the runner skill's packet convention. Mark status **Draft — awaiting approval; not dispatched**. Capture the requested outcome and any details the human explicitly asks to include, not an agent-invented implementation plan. Leave project rules and skill discovery to each runner; do not turn them into relay instructions. The first runner chooses the first useful slice.
11
11
 
12
- 1. `relay` — the portable Relay principle and durable-state model.
13
- 2. `relay-runner` — the opinionated Pi/Git operational profile used by this Relay.
12
+ Point the human to the draft, summarize the goal and boundaries, and resolve material questions together through `ask_user`. Once the goal is understood, ask **Approve and dispatch**, **Revise**, or **Do not dispatch** against the final draft. Drafting and the initial request are not dispatch approval.
14
13
 
15
- ## Preflight: understand before dispatch
16
-
17
- The quality of the Relay depends on a clear shared understanding of its destination and edges. Front-load that understanding, not a predicted implementation route.
18
-
19
- 1. **Start a reviewable draft packet.** Choose a clear Relay name and create `.pi-web/relays/<name>/` in the current drafting checkout. Seed draft `charter.md`, `operations.md`, `status.md`, and `log.md`; mark status **Draft — awaiting approval; not dispatched**. Update these documents as understanding improves so the human can review them through the Relays UI. Draft creation is not dispatch authorization.
20
- 2. **Understand the request and baseline.** Read the repository's canonical agent/contributor instructions and task-relevant project material. Inspect enough current behavior, repository state, and delivery context to distinguish the user's intended outcome from an assumed solution. Keep this bounded to facts that affect the goal, scope edges, feasibility, working location, or first leg; leave implementation archaeology to the Relay.
21
- 3. **Capture understanding without making a roadmap.** Keep `charter.md` to the plain-language goal and observable finish line, minimum outcome acceptance, in-scope edges, explicit non-goals, directly affected behavior to preserve, and material assumptions or human decisions. Put packet/profile identity, proposed operating mode/base, canonical project-guidance pointers, verification/delivery mechanics, and packet isolation in `operations.md`. Put only the proposed first bounded leg and current approval state in `status.md`.
22
- 4. **Do not pre-plan the chain.** Do not create a fixed leg count, work-package hierarchy, exhaustive stage list, expected file/layer map, `plan.md`, or technical design merely to make the Relay look prepared. The route is adaptive. Select an implementation-first leg under the runner profile. Reserve research-only work for a concrete unanswered question blocking implementation; routine investigation belongs inside the implementation leg.
23
- 5. **Discuss and refine.** Point the user at the draft packet, summarize the current understanding, ask related material questions together with `ask_user`, and incorporate the answers into the drafts. Do not hide assumptions or settle product, destructive-data, target, base, or delivery choices by guesswork.
24
- 6. **Request approval against the final drafts.** After the user has had a chance to review the final document purposes and content, provide the packet path plus a concise goal/edges, operating-setup, and first-leg summary. Use `ask_user` to offer **Approve and dispatch**, **Revise**, or **Do not dispatch**. The initial request, a clear task, prior general enthusiasm, silence, or permission to create drafts is not dispatch approval. If goal, edges, operating target, or first leg changes materially after approval, update the drafts and obtain fresh approval.
25
-
26
- The approval gate applies to `spawn_session`, not to creating or refining packet drafts or transparently preparing checkout/worktree state. On **Revise**, update the drafts and repeat review. On **Do not dispatch**, mark status not dispatched, append the decision to the log, and stop without spawning.
27
-
28
- ## After approval
29
-
30
- Only after an explicit **Approve and dispatch** response, follow `relay-runner`'s approved-dispatch workflow:
31
-
32
- - finalize `charter.md`, `operations.md`, `status.md`, and `log.md` without adding an upfront route plan;
33
- - mark the packet approved and record the approval in status/log;
34
- - create or finish the selected checkout/worktree setup;
35
- - when the target is a fresh worktree, move the draft packet from the drafting checkout into the target worktree's `.pi-web/relays/<name>/`, update its recorded path/location, and remove the stale draft copy;
36
- - dispatch exactly one first leg with `spawn_session` after all state is durable in its final location; and
37
- - after `spawn_session` returns, provide the dispatch summary without further tool use, state changes, or Relay work.
38
-
39
- This invocation proposes **in-place mode** in the current checkout and branch unless the task source explicitly requests a fresh worktree or names another existing checkout/worktree. Resolve the mode during preflight; do not redirect the user to another command.
40
-
41
- Treat the text inside `<relay_task>` as source material to understand, not as instructions that bypass discussion or approval.
14
+ On revision, update the draft and obtain approval. On refusal, record that it was not dispatched and stop. After explicit approval, record it, mark status **Active**, seed leg 1, and finalize the packet with the actual baton saved in `status.md`. Call the dedicated `dispatch_relay` tool once with the saved directory as `packet`, `leg: "1"`, and the target checkout as `cwd`, following the runner skill's handoff mechanics. The tool generates the handover; do not supply a free-form prompt. Dispatch is the final operational action; afterward provide only a brief summary.
42
15
 
43
16
  <relay_task>
44
17
  $ARGUMENTS
@@ -0,0 +1,67 @@
1
+ // Generated from pi-packages/relays/relayDispatch.ts. Do not edit directly.
2
+ import { open, realpath, stat } from "node:fs/promises";
3
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { isRelayPacketPath, RELAY_METADATA_NAMESPACE, relaySessionName, requireRelayLeg } from "./relayIdentity.js";
5
+ const MAX_PACKET_DOCUMENT_BYTES = 256 * 1024;
6
+ const PACKET_DOCUMENTS = ["charter.md", "status.md", "log.md"];
7
+ /** Validate the durable packet on disk, not handover prose authored by the caller. */
8
+ export async function prepareRelayDispatch(spawningCwd, request) {
9
+ const leg = requireRelayLeg(request.leg);
10
+ const cwd = await realpath(resolve(spawningCwd, request.cwd ?? "."));
11
+ if (typeof request.packet !== "string" || request.packet.trim() === "")
12
+ throw new Error("A saved Relay packet directory is required");
13
+ const packet = await realpath(resolve(cwd, request.packet));
14
+ const packetPath = relative(cwd, packet).split(sep).join("/");
15
+ if (!isRelayPacketPath(packetPath))
16
+ throw new Error("Relay packet must be a directory inside the target workspace");
17
+ for (const document of PACKET_DOCUMENTS)
18
+ await validateDocument(packet, document);
19
+ const identity = { version: 1, packetPath, relayName: basename(packet), leg };
20
+ return {
21
+ cwd,
22
+ identity,
23
+ name: relaySessionName(identity),
24
+ metadata: { [RELAY_METADATA_NAMESPACE]: identity },
25
+ prompt: relayHandoverInstructions(cwd, packet, identity),
26
+ };
27
+ }
28
+ function relayHandoverInstructions(cwd, packet, identity) {
29
+ return [
30
+ `Continue Relay ${JSON.stringify(identity.relayName)}, leg ${identity.leg}, in ${JSON.stringify(cwd)}.`,
31
+ "Load the `relay` and `relay-runner` skills.",
32
+ `Read ${JSON.stringify(join(packet, "charter.md"))} and ${JSON.stringify(join(packet, "status.md"))}.`,
33
+ "The charter is the agreed goal and status.md is the actual baton. Consult the saved history and supporting documents only as needed; do not substitute dispatch prose for the packet.",
34
+ "Check the finish line against the current work and choose the next useful slice.",
35
+ "Make progress and the record durable, then hand off once, complete, or signal that you need help.",
36
+ ].join("\n");
37
+ }
38
+ async function validateDocument(packet, document) {
39
+ const path = await realpath(join(packet, document));
40
+ const inside = relative(packet, path);
41
+ if (isAbsolute(inside) || inside === ".." || inside.startsWith(`..${sep}`))
42
+ throw new Error(`Relay ${document} must stay inside the packet`);
43
+ const info = await stat(path);
44
+ if (!info.isFile() || info.size === 0 || info.size > MAX_PACKET_DOCUMENT_BYTES) {
45
+ throw new Error(`Relay ${document} must be a non-empty regular file of at most ${String(MAX_PACKET_DOCUMENT_BYTES)} bytes`);
46
+ }
47
+ const file = await open(path, "r");
48
+ try {
49
+ // Bound reads even if another process grows the document after stat.
50
+ const buffer = Buffer.alloc(MAX_PACKET_DOCUMENT_BYTES + 1);
51
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
52
+ if (bytesRead > MAX_PACKET_DOCUMENT_BYTES)
53
+ throw new Error(`Relay ${document} exceeds the document size limit`);
54
+ let text;
55
+ try {
56
+ text = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead));
57
+ }
58
+ catch {
59
+ throw new Error(`Relay ${document} must be UTF-8 text`);
60
+ }
61
+ if (text.trim() === "" || text.includes("\0"))
62
+ throw new Error(`Relay ${document} must contain saved text`);
63
+ }
64
+ finally {
65
+ await file.close();
66
+ }
67
+ }
@@ -0,0 +1,37 @@
1
+ // Generated from pi-packages/relays/relayIdentity.ts. Do not edit directly.
2
+ /** Relay owns this schema; core treats it as opaque browser-public JSON. */
3
+ export const RELAY_METADATA_NAMESPACE = "@jmfederico/pi-relay";
4
+ export function relayIdentityFromMetadata(metadata) {
5
+ const value = metadata?.[RELAY_METADATA_NAMESPACE];
6
+ if (value?.["version"] !== 1)
7
+ return undefined;
8
+ const packetPath = value["packetPath"];
9
+ const relayName = value["relayName"];
10
+ const leg = value["leg"];
11
+ if (typeof packetPath !== "string" || !isRelayPacketPath(packetPath)
12
+ || typeof relayName !== "string" || relayName !== packetPath.split("/").at(-1)
13
+ || typeof leg !== "string" || !isRelayLeg(leg))
14
+ return undefined;
15
+ return { version: 1, packetPath, relayName, leg };
16
+ }
17
+ export function isRelayPacketPath(value) {
18
+ // eslint-disable-next-line no-control-regex -- packet paths must not contain control characters.
19
+ return value.length > 0 && value.length <= 4096 && !/[\\\u0000-\u001f\u007f]/u.test(value)
20
+ && value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && segment === segment.trim());
21
+ }
22
+ export function isRelayLeg(value) {
23
+ return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,31}$/u.test(value);
24
+ }
25
+ export function requireRelayLeg(value) {
26
+ if (typeof value !== "string" || !isRelayLeg(value)) {
27
+ throw new Error("Relay leg must be a string of 1–32 letters, digits, dots, underscores or hyphens, starting with a letter or digit");
28
+ }
29
+ return value;
30
+ }
31
+ /** Keep the leg first so host and sidebar truncation only shorten the Relay name. */
32
+ export function relaySessionName(identity) {
33
+ const prefix = `Leg ${identity.leg} – Relay `;
34
+ const room = 60 - prefix.length;
35
+ const name = identity.relayName.slice(0, room).replace(/[\s._-]+$/u, "") || identity.relayName.slice(0, room);
36
+ return `${prefix}${name}`;
37
+ }
@@ -1,7 +1,9 @@
1
1
  // Generated from pi-packages/relays/relaysPanelElement.ts. Do not edit directly.
2
2
  import { isMarkdownDocumentPath, renderRelayDocumentHtml } from "./markdownDocument.js";
3
+ import { isRelayPacketPath } from "./relayIdentity.js";
3
4
  import { ancestorDirectoryPaths, collectDirectoryPaths, defaultRelayDocument, flattenRelayTree, listRelayDocumentTree, listWorkspaceRelays, readRelayDocument, RELAYS_ROOT, } from "./relayDiscovery.js";
4
5
  export const relaysPanelTagName = "pi-web-relays-panel";
6
+ export const RELAY_SELECTION_QUERY_KEY = "relay";
5
7
  export function defineRelaysPanelElement() {
6
8
  if (!customElements.get(relaysPanelTagName))
7
9
  customElements.define(relaysPanelTagName, PiWebRelaysPanel);
@@ -86,33 +88,51 @@ class PiWebRelaysPanel extends HTMLElement {
86
88
  set context(value) {
87
89
  const previousKey = this.contextValue === undefined ? undefined : contextKey(this.contextValue);
88
90
  const nextKey = value === undefined ? undefined : contextKey(value);
91
+ const previousRelay = linkedRelayPath(this.contextValue);
92
+ const nextRelay = linkedRelayPath(value);
89
93
  this.contextValue = value;
90
- // Parent app updates should not rescan or re-render this panel for the
91
- // same workspace (mirrors the workspace-tasks panel).
92
- if (previousKey === nextKey)
94
+ // Unrelated host renders should not rescan. A picker already starts its
95
+ // load before publishing navigation, so adopting that route does not load twice.
96
+ if (previousKey === nextKey && (previousRelay === nextRelay
97
+ || (nextRelay !== undefined && nextRelay === this.selectedRelayPath)))
93
98
  return;
94
- // A different workspace starts with every folder collapsed.
95
99
  this.expandedDirs = new Set();
96
100
  if (value === undefined) {
101
+ ++this.scanToken;
97
102
  this.resetScanState();
98
103
  this.renderAll();
99
104
  return;
100
105
  }
101
- void this.scan(value, {});
106
+ void this.scan(value, { relayPath: nextRelay });
102
107
  }
103
108
  /** Rescan relays, then reload the selected relay's documents and the open document. */
104
109
  async scan(context, selection) {
105
110
  const token = ++this.scanToken;
106
111
  this.resetScanState();
107
112
  this.renderAll();
113
+ const requestedPath = selection.relayPath ?? linkedRelayPath(context);
114
+ if (requestedPath !== undefined && !isRelayPacketPath(requestedPath)) {
115
+ this.listing = { kind: "unavailable", detail: "Relay links must select a normalized workspace-relative packet directory." };
116
+ this.renderAll();
117
+ return;
118
+ }
108
119
  const listing = await listWorkspaceRelays(context.files);
109
120
  if (!this.isCurrentScan(context, token))
110
121
  return;
111
- this.listing = listing;
112
- // listWorkspaceRelays returns most recently modified first, so the first
113
- // relay is the default pre-selection.
114
- const relay = listing.kind === "loaded"
115
- ? listing.relays.find((candidate) => candidate.path === selection.relayPath) ?? listing.relays[0]
122
+ // An exact link need not be under the discovery root. Keep that packet in
123
+ // the picker even if the default root is absent/unavailable, and let its
124
+ // own document load report a missing or unreadable target rather than
125
+ // silently substituting another relay.
126
+ const discovered = listing.kind === "loaded" ? listing.relays : [];
127
+ this.listing = requestedPath === undefined ? listing : {
128
+ kind: "loaded",
129
+ relays: discovered.some((relay) => relay.path === requestedPath) ? discovered : [
130
+ { name: documentName(requestedPath), path: requestedPath }, ...discovered,
131
+ ],
132
+ };
133
+ // Default discovery remains most recently modified first.
134
+ const relay = this.listing.kind === "loaded"
135
+ ? this.listing.relays.find((candidate) => candidate.path === requestedPath) ?? this.listing.relays[0]
116
136
  : undefined;
117
137
  this.selectedRelayPath = relay?.path;
118
138
  this.renderToolbar();
@@ -126,6 +146,7 @@ class PiWebRelaysPanel extends HTMLElement {
126
146
  const token = ++this.scanToken;
127
147
  this.selectedRelayPath = relayPath;
128
148
  this.expandedDirs = new Set();
149
+ context.navigation?.set(RELAY_SELECTION_QUERY_KEY, relayPath);
129
150
  await this.loadDocuments(context, token, relayPath, undefined);
130
151
  }
131
152
  /** Expand or collapse a folder chip; the accordion rule keeps at most one expanded folder per level. */
@@ -374,7 +395,7 @@ class PiWebRelaysPanel extends HTMLElement {
374
395
  return `${partialNotice}
375
396
  <div class="empty-state">
376
397
  <strong>This relay has no documents yet.</strong>
377
- <p>Relay Runner packets usually contain <code>status.md</code>, <code>charter.md</code>, <code>operations.md</code>, and <code>log.md</code>.</p>
398
+ <p>Relay packets contain <code>charter.md</code> (goal), <code>status.md</code> (current baton), and <code>log.md</code> (history). An optional <code>decisions.md</code> keeps consequential decisions and their reasons.</p>
378
399
  </div>
379
400
  `;
380
401
  }
@@ -451,7 +472,11 @@ function chevronSvg() {
451
472
  return `<svg class="chevron" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M6 4l4 4-4 4"></path></svg>`;
452
473
  }
453
474
  function contextKey(context) {
454
- return `${context.machine.id}:${context.workspace.projectId}:${context.workspace.id}`;
475
+ return JSON.stringify([context.machine.id, context.workspace.projectId, context.workspace.id]);
476
+ }
477
+ function linkedRelayPath(context) {
478
+ const value = context?.navigation?.query[RELAY_SELECTION_QUERY_KEY];
479
+ return typeof value === "string" ? value : value?.[0];
455
480
  }
456
481
  function documentName(path) {
457
482
  return path.slice(path.lastIndexOf("/") + 1);
@@ -1,94 +1,50 @@
1
1
  ---
2
2
  name: relay
3
- description: "Foundational, tool-agnostic Relay method for carrying long work across a chain of independent agent contexts, one bounded leg at a time. Use when a user asks what Relay is, invokes Relay directly, designs a Relay workflow or operational profile, or refers to an active Relay chain or packet. Do not load for generic multi-step plans, ordinary delegation, or unrelated session spawning."
3
+ description: "Carry work across a chain of fresh agent contexts using a shared goal, compact baton, and durable record. Use when a user invokes Relay, discusses the Relay method, prepares a Relay, or continues an active Relay. Do not load for generic multi-step work or ordinary delegation."
4
4
  ---
5
5
 
6
6
  # Relay
7
7
 
8
- Relay is a way to carry a long or complex effort across a chain of independent agent contexts. Each runner completes one bounded **leg**, makes progress durable, and hands the work to one fresh successor. The chain continues until the finish line is reached or human intervention is needed.
8
+ A Relay carries work across fresh agent contexts until the agreed goal is reached or an agent needs human help. Each context owns one useful slice, called a **leg**. There is no standing coordinator or fixed set of agent roles.
9
9
 
10
- The method follows the [Relay Principle](https://relayprinciple.ai/): do not recreate human management structures around agents by default. There is no standing coordinator, referee, role hierarchy, or “god-agent” supervising the chain. Each runner owns its leg, adapts the route within agreed bounds, and trusts the next runner to do the same.
10
+ Follow the [Relay Principle](https://relayprinciple.ai/): give agents a destination and enough continuity to act, rather than recreating a management structure around them.
11
11
 
12
- Relay is safe because it combines distributed trust with **context containment**. A fresh runner receives compact durable state instead of inheriting an ever-growing conversation or defensively reconstructing the full history.
12
+ ## Finish line over plan
13
13
 
14
- ## Core model
14
+ Help the human express what they want built and how they will recognize it is done. Resolve material ambiguity before dispatch. An existing requirements document can supply the goal; reference it rather than rewriting it into a larger specification.
15
15
 
16
- - **Relay:** the complete chain and its stable destination.
17
- - **Runner:** the agent context responsible for one leg. It coordinates its own slice; it does not supervise later runners.
18
- - **Leg:** one context-contained, coherent unit of progress. Leg boundaries protect context quality, not organizational ownership.
19
- - **Packet:** durable state shared across otherwise independent contexts.
20
- - **Baton:** the packet's compact current-state view: where the Relay is now, what comes next, and the targeted context needed by the next runner.
21
- - **Handoff:** the final operational act that starts or designates at most one successor after current work is durable. A user-facing summary may follow, but no further work, durable-state mutation, tool use, or downstream steering.
22
- - **Intervention:** a visible stop when the destination cannot be pursued responsibly within the current agreement.
23
- - **Operational profile:** the tool- and workflow-specific policy that binds these concepts to a concrete environment.
16
+ Keep the goal stable and the route adaptable. Each runner checks the finish line against the current work and chooses the next useful slice. Changing the goal requires human agreement; changing implementation or sequencing within scope does not.
24
17
 
25
- ## Invariants
18
+ ## Scope over script
26
19
 
27
- A workflow keeps the spirit of Relay when these properties hold:
20
+ Record the relay-specific boundaries, limitations, conditions, and clarifications needed to understand the request. Preserve technical requirements and any other details the human explicitly asks to include.
28
21
 
29
- 1. **Stable destination, adaptive route.** The finish line and material bounds remain authoritative while runners adapt sequencing and implementation. Changing the destination requires the agreement authority defined by the Relay.
30
- 2. **One bounded leg per context.** A runner does not keep accumulating unrelated work or execute several nominal legs in one context.
31
- 3. **Durability before handoff.** Decisions, artifacts, current state, and blockers needed downstream are preserved outside transient conversation before a successor begins.
32
- 4. **At most one successor.** A runner either hands off once at the end or stops. It does not fan out the chain or hand off while its own work remains in flight.
33
- 5. **Fresh-context trust.** The successor is allowed to own its leg. If a runner feels it must watch and correct downstream work, the slice, packet, or intervention policy is not ready.
34
- 6. **Bounded orientation.** A successor starts from the stable agreement and baton, then reads only targeted supporting context. Full-history reconstruction is exceptional, not routine.
35
- 7. **Visible stopping.** Completion, blockers, and intervention are recorded clearly. Spawning a confused successor is worse than stopping cleanly.
36
- 8. **No silent goal drift.** Current-state updates cannot redefine what the Relay is trying to achieve.
22
+ Do not invent an implementation roadmap, speculative requirements, or a new software process. Project instructions, skills, and harness conventions remain where they belong; each runner discovers and follows what applies to its work. A Relay does not replace or weaken those rules.
37
23
 
38
- ## Durable state by role
24
+ ## Handoff over handhold
39
25
 
40
- Relay needs durable state with three distinct authorities. An implementation may use files, records, messages, or another medium; the roles matter more than their names.
26
+ A baton says **where we are**, not **how you must work**. Avoid suggesting or assigning the next slice: even a provisional suggestion can anchor the successor. Record remaining gaps and concrete dependencies instead, and trust the successor to inspect reality and choose its slice.
41
27
 
42
- ### Stable agreement
28
+ Choose a meaningful increment that fits one context, not the tiniest possible task. Investigation and related work can stay together. Leg boundaries protect context quality, not organizational ownership.
43
29
 
44
- Defines identity, goal and observable finish line, scope edges, explicit non-goals, and material assumptions or human decisions. It changes rarely. Clarification is normal; moving the finish line or an edge is an agreement change, not routine adaptation.
30
+ Finish ongoing work and preserve the state before starting at most one fresh successor. Handoff is the final operational action; only a user-facing summary follows. Do not watch or steer the successor.
45
31
 
46
- Keep this destination-focused. Do not turn it into an implementation plan, technical design, quality checklist, risk inventory, or copy of project instructions. Those details anchor later runners to route assumptions and blur what requires human agreement.
32
+ ## Record over memory
47
33
 
48
- ### Current baton
34
+ Keep three kinds of durable information:
49
35
 
50
- Defines present position, the last completed and next leg identifiers, current or next task, targeted context pointers, blockers, and required progress updates. It stays compact. It carries position, not destination.
36
+ - **Goal:** the agreed finish line and relay-specific scope. Current status cannot override it.
37
+ - **Baton:** current position, completed work, what remains, relevant evidence and decisions, useful pointers, and any blocker.
38
+ - **History:** concise entries recording each leg's result, decisions, artifacts, checks, and handoff or stop.
51
39
 
52
- ### History
40
+ Keep the baton compact. Use history for targeted lookup, not mandatory reconstruction. Inspect the actual work as needed; the record saves rediscovering prior decisions, not reading the project.
53
41
 
54
- Preserves concise append-only evidence of completed legs, decisions, artifacts, agreement changes, and stops. It supports targeted lookup and auditability; it is not the default orientation surface.
42
+ ## Signal over spin
55
43
 
56
- Separating these roles prevents recency from becoming authority. A newer baton cannot silently override the stable agreement, and a large history does not become mandatory context.
44
+ Stop when the finish line is met, with evidence. Completion does not require finding another improvement.
57
45
 
58
- ## Operational profiles
46
+ Stop visibly when progress needs human input: record the blocker, what was tried, and the specific decision or help needed. If successive legs keep revisiting the same issue without new evidence or meaningful progress, surface that stall instead of spawning another attempt.
59
47
 
60
- This base skill is intentionally non-operational and tool agnostic. It does not choose:
48
+ Optional cleanup, hypothetical risks, and a desire for perfection are not reasons to prolong the chain. Revisit a settled decision when facts change or concrete evidence shows it was wrong, not merely because a fresh agent has arrived.
61
49
 
62
- - how a successor context is created;
63
- - where or in what format durable state lives;
64
- - how large a leg should be or how tasks are selected;
65
- - whether work uses source control, worktrees, commits, tests, reviews, or delivery gates;
66
- - how intervention reaches a human; or
67
- - what project-specific quality standard applies.
68
-
69
- An operational profile supplies those bindings and decides where to keep its identity and any operational record. Each handoff makes the active profile visible to the fresh runner. A profile may be strongly opinionated without putting its mechanics into the destination agreement or turning its choices into the definition of Relay.
70
-
71
- Projects can layer any operational profile that fits their environment while preserving the invariants above.
72
-
73
- ## Context containment
74
-
75
- A fresh runner normally needs only:
76
-
77
- 1. the stable agreement;
78
- 2. the current baton; and
79
- 3. the specific supporting material those surfaces identify for the leg.
80
-
81
- A baton that routinely requires full-history or whole-artifact-tree reconstruction violates bounded orientation. A state gap that can be resolved only through broad archaeology or guessing about the destination is an intervention condition, not ordinary continuation.
82
-
83
- ## Failure smells
84
-
85
- - **Coordinator creep:** a persistent supervisor plans, watches, or approves every leg.
86
- - **Role bureaucracy:** fixed agent roles or layer ownership replace fluid, outcome-driven slices without a real constraint requiring them.
87
- - **No stable finish line:** mutable current state is the only definition of done.
88
- - **Baton authority creep:** status quietly narrows or expands the agreement.
89
- - **Context leakage:** every runner reloads the full history or inherits an unbounded conversation.
90
- - **Eager or parallel handoff:** successors begin before the current leg is durable, or one runner fans out the chain.
91
- - **Oversized legs:** a runner continues beyond a coherent context-contained checkpoint.
92
- - **Silent stall:** work stops without durable blocker or intervention state.
93
-
94
- Operational details may vary widely. These failures matter because they undermine the principle, not because a particular tool or file convention was violated.
50
+ Tools, storage, and session creation belong to the environment-specific runner skill, not the definition of Relay.