openplanr 2.6.1 → 2.6.3

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 (48) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/dashboard/.vite/manifest.json +1 -1
  3. package/dist/dashboard/assets/{index-BaARCc1f.js → index-AeydhSKa.js} +2 -2
  4. package/dist/dashboard/assets/{index-BaARCc1f.js.map → index-AeydhSKa.js.map} +1 -1
  5. package/dist/dashboard/dashboard-manifest.json +8 -8
  6. package/dist/dashboard/index.html +1 -1
  7. package/dist/services/diagram-artifact-service.d.ts +26 -0
  8. package/dist/services/diagram-artifact-service.d.ts.map +1 -1
  9. package/dist/services/diagram-artifact-service.js +10 -0
  10. package/dist/services/diagram-artifact-service.js.map +1 -1
  11. package/lib/host-packages/adapter-registry.json +5 -5
  12. package/lib/host-packages/capability-map.json +1 -1
  13. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  14. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  15. package/lib/host-packages/claude/openplanr/.openplanr-content.json +11 -11
  16. package/lib/host-packages/claude/openplanr/skills/design/scripts/design.mjs +80 -1
  17. package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/protocol/package.json +5 -1
  18. package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design.mjs +80 -1
  19. package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/protocol/package.json +5 -1
  20. package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design.mjs +80 -1
  21. package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/protocol/package.json +5 -1
  22. package/lib/host-packages/claude/openplanr/skills/diagram/references/diagram-fidelity.md +14 -0
  23. package/lib/host-packages/claude/openplanr/skills/plan/scripts/design.mjs +80 -1
  24. package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/protocol/package.json +5 -1
  25. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +11 -11
  26. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  27. package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design.mjs +80 -1
  28. package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/protocol/package.json +5 -1
  29. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design.mjs +80 -1
  30. package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/protocol/package.json +5 -1
  31. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design.mjs +80 -1
  32. package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/protocol/package.json +5 -1
  33. package/lib/host-packages/cursor/openplanr/rules/planr-diagram/references/diagram-fidelity.md +14 -0
  34. package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design.mjs +80 -1
  35. package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/protocol/package.json +5 -1
  36. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  37. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  38. package/lib/host-packages/openai/openplanr/.openplanr-content.json +11 -11
  39. package/lib/host-packages/openai/openplanr/skills/design/scripts/design.mjs +80 -1
  40. package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/protocol/package.json +5 -1
  41. package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design.mjs +80 -1
  42. package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/protocol/package.json +5 -1
  43. package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design.mjs +80 -1
  44. package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/protocol/package.json +5 -1
  45. package/lib/host-packages/openai/openplanr/skills/diagram/references/diagram-fidelity.md +14 -0
  46. package/lib/host-packages/openai/openplanr/skills/plan/scripts/design.mjs +80 -1
  47. package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/protocol/package.json +5 -1
  48. package/package.json +2 -2
@@ -14910,6 +14910,7 @@ function createArtifactReviewServer({
14910
14910
  throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Artifact review instance id is invalid.");
14911
14911
  }
14912
14912
  const sessions = /* @__PURE__ */ new Map();
14913
+ const ownerSessions = /* @__PURE__ */ new Map();
14913
14914
  let port = null;
14914
14915
  let closePromise = null;
14915
14916
  let pendingRegistrations = 0;
@@ -14917,7 +14918,7 @@ function createArtifactReviewServer({
14917
14918
  let draining = false;
14918
14919
  let emptyTimer = null;
14919
14920
  const stageRuntime = () => readFileSync11(STAGE_RUNTIME_PATH, "utf8");
14920
- const idle = () => sessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
+ const idle = () => sessions.size === 0 && ownerSessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
14922
  const scheduleEmpty = () => {
14922
14923
  if (emptyTimer || draining || typeof onEmpty !== "function") return;
14923
14924
  emptyTimer = setTimeout(async () => {
@@ -15042,6 +15043,55 @@ function createArtifactReviewServer({
15042
15043
  notFound(res, { head });
15043
15044
  return;
15044
15045
  }
15046
+ if (segments[0] === "o") {
15047
+ const owner = safeSessionId(segments[1]) && ownerSessions.get(segments[1]);
15048
+ if (draining || !sessionMatches(owner, segments[2])) {
15049
+ notFound(res, { head });
15050
+ return;
15051
+ }
15052
+ if (segments.length === 3 && !trailingSlash && ["GET", "HEAD"].includes(req.method)) {
15053
+ send(res, 308, "", { location: `/o/${owner.id}/${owner.capability}/` }, { head });
15054
+ return;
15055
+ }
15056
+ const request2 = Promise.resolve().then(() => owner.handleRequest({
15057
+ req,
15058
+ segments: segments.slice(3),
15059
+ head,
15060
+ origin: `http://${LOOPBACK_HOST}:${port}`,
15061
+ recoveryScope: owner.recoveryScope
15062
+ }));
15063
+ owner.pending.add(request2);
15064
+ let response;
15065
+ try {
15066
+ response = await request2;
15067
+ } finally {
15068
+ owner.pending.delete(request2);
15069
+ }
15070
+ if (!response) {
15071
+ notFound(res, { head });
15072
+ return;
15073
+ }
15074
+ if (response.kind === "asset") {
15075
+ const mediaTypes = { document: "text/html", runtime: "text/javascript", stylesheet: "text/css" };
15076
+ if (!Object.hasOwn(mediaTypes, response.asset) || typeof response.body !== "string") throw new Error("Invalid owner asset response.");
15077
+ send(res, response.status, response.body, {
15078
+ ...parentHeaders(),
15079
+ "content-type": `${mediaTypes[response.asset]}; charset=utf-8`,
15080
+ "content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
15081
+ }, { head });
15082
+ return;
15083
+ }
15084
+ send(res, response.status, JSON.stringify(response.body), {
15085
+ // Rejections may precede body consumption (for example Content-Length
15086
+ // above the limit). Do not reuse a socket containing unread body bytes.
15087
+ ...response.status >= 400 ? { connection: "close" } : {},
15088
+ "content-type": "application/json; charset=utf-8",
15089
+ "cross-origin-resource-policy": "same-origin",
15090
+ "content-security-policy": "default-src 'none'; frame-ancestors 'none'",
15091
+ "x-frame-options": "DENY"
15092
+ }, { head });
15093
+ return;
15094
+ }
15045
15095
  if (segments.length < 3 || segments[0] !== "r" || !safeSessionId(segments[1])) {
15046
15096
  notFound(res, { head });
15047
15097
  return;
@@ -15200,6 +15250,32 @@ function createArtifactReviewServer({
15200
15250
  controlToken,
15201
15251
  instanceId,
15202
15252
  sessionCount: () => sessions.size,
15253
+ /** Trusted local authority only; this operation has no HTTP control route. */
15254
+ registerOwnerSession({ handleRequest } = {}) {
15255
+ if (draining || closePromise || typeof handleRequest !== "function") {
15256
+ throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Local owner session cannot be registered.");
15257
+ }
15258
+ const id4 = mintCapabilityToken({ bytes: SESSION_ID_BYTES });
15259
+ const owner = {
15260
+ id: id4,
15261
+ capability: mintCapabilityToken({ bytes: SESSION_TOKEN_BYTES }),
15262
+ recoveryScope: `diagram-owner_${id4}`,
15263
+ handleRequest,
15264
+ pending: /* @__PURE__ */ new Set()
15265
+ };
15266
+ ownerSessions.set(id4, owner);
15267
+ return Object.freeze({
15268
+ sessionId: id4,
15269
+ capability: owner.capability,
15270
+ recoveryScope: owner.recoveryScope,
15271
+ path: `/o/${id4}/${owner.capability}/`,
15272
+ async close() {
15273
+ ownerSessions.delete(id4);
15274
+ await Promise.allSettled([...owner.pending]);
15275
+ if (idle()) scheduleEmpty();
15276
+ }
15277
+ });
15278
+ },
15203
15279
  isIdle: idle,
15204
15280
  accepting: () => !draining && !closePromise,
15205
15281
  beginCloseIfIdle() {
@@ -15224,6 +15300,9 @@ function createArtifactReviewServer({
15224
15300
  emptyTimer = null;
15225
15301
  closePromise = (async () => {
15226
15302
  sessions.clear();
15303
+ const pendingOwners = [...ownerSessions.values()].flatMap((owner) => [...owner.pending]);
15304
+ ownerSessions.clear();
15305
+ await Promise.allSettled(pendingOwners);
15227
15306
  await closeHttpServer(server);
15228
15307
  })();
15229
15308
  return closePromise;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openplanr/protocol",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Canonical OpenPlanr schemas, registries, typed errors, and portable contract validation",
6
6
  "license": "MIT",
@@ -83,6 +83,10 @@
83
83
  "./enterprise-contracts": {
84
84
  "types": "./src/enterprise-contracts.d.mts",
85
85
  "import": "./src/enterprise-contracts.mjs"
86
+ },
87
+ "./diagram-authoring-contracts": {
88
+ "types": "./src/diagram-authoring-contracts.d.mts",
89
+ "import": "./src/diagram-authoring-contracts.mjs"
86
90
  }
87
91
  },
88
92
  "files": [
@@ -4,6 +4,20 @@ Use this reference when the user requests Mermaid or Excalidraw editing, imports
4
4
  an existing source, or asks why a projection was omitted.
5
5
 
6
6
  - Canonical `.planr-diagram.json` is the semantic source and is always editable.
7
+ - For the v1.13 flowchart authoring path, the `.planr-diagram-bundle.json` holds
8
+ the semantic document, presentation, exact original Mermaid text, and source
9
+ correspondence. Use `@openplanr/artifact/diagram-authoring`'s
10
+ `previewMermaidCopy`, `adoptMermaidCopy`, and `exportMermaidCopy` for copy
11
+ interchange. Preview never saves; adoption requires acknowledgement of that
12
+ exact preview's fidelity losses. Saving the bundle is a separate owner action.
13
+ - The certified Mermaid copy subset supports explicit flowchart node IDs,
14
+ rectangle/rounded/decision/cylinder shapes, directed/bidirectional/undirected
15
+ edges, labels, and nested subgraphs. It does not infer lanes or preserve
16
+ coordinates, manual routes, annotations, styling, or exact source formatting
17
+ on export. Keep the complete bundle when these matter. Copy import/export
18
+ never creates a repository source link or watcher.
19
+ - Legacy sequence diagrams remain readable and renderable; they are not in the
20
+ editable Mermaid copy certification.
7
21
  - Mermaid is emitted only when the selected grammar has a certified projection.
8
22
  `editable`, `partial`, and `unsupported` are honest compatibility levels, not
9
23
  success or failure labels.
@@ -14910,6 +14910,7 @@ function createArtifactReviewServer({
14910
14910
  throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Artifact review instance id is invalid.");
14911
14911
  }
14912
14912
  const sessions = /* @__PURE__ */ new Map();
14913
+ const ownerSessions = /* @__PURE__ */ new Map();
14913
14914
  let port = null;
14914
14915
  let closePromise = null;
14915
14916
  let pendingRegistrations = 0;
@@ -14917,7 +14918,7 @@ function createArtifactReviewServer({
14917
14918
  let draining = false;
14918
14919
  let emptyTimer = null;
14919
14920
  const stageRuntime = () => readFileSync11(STAGE_RUNTIME_PATH, "utf8");
14920
- const idle = () => sessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
+ const idle = () => sessions.size === 0 && ownerSessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
14922
  const scheduleEmpty = () => {
14922
14923
  if (emptyTimer || draining || typeof onEmpty !== "function") return;
14923
14924
  emptyTimer = setTimeout(async () => {
@@ -15042,6 +15043,55 @@ function createArtifactReviewServer({
15042
15043
  notFound(res, { head });
15043
15044
  return;
15044
15045
  }
15046
+ if (segments[0] === "o") {
15047
+ const owner = safeSessionId(segments[1]) && ownerSessions.get(segments[1]);
15048
+ if (draining || !sessionMatches(owner, segments[2])) {
15049
+ notFound(res, { head });
15050
+ return;
15051
+ }
15052
+ if (segments.length === 3 && !trailingSlash && ["GET", "HEAD"].includes(req.method)) {
15053
+ send(res, 308, "", { location: `/o/${owner.id}/${owner.capability}/` }, { head });
15054
+ return;
15055
+ }
15056
+ const request2 = Promise.resolve().then(() => owner.handleRequest({
15057
+ req,
15058
+ segments: segments.slice(3),
15059
+ head,
15060
+ origin: `http://${LOOPBACK_HOST}:${port}`,
15061
+ recoveryScope: owner.recoveryScope
15062
+ }));
15063
+ owner.pending.add(request2);
15064
+ let response;
15065
+ try {
15066
+ response = await request2;
15067
+ } finally {
15068
+ owner.pending.delete(request2);
15069
+ }
15070
+ if (!response) {
15071
+ notFound(res, { head });
15072
+ return;
15073
+ }
15074
+ if (response.kind === "asset") {
15075
+ const mediaTypes = { document: "text/html", runtime: "text/javascript", stylesheet: "text/css" };
15076
+ if (!Object.hasOwn(mediaTypes, response.asset) || typeof response.body !== "string") throw new Error("Invalid owner asset response.");
15077
+ send(res, response.status, response.body, {
15078
+ ...parentHeaders(),
15079
+ "content-type": `${mediaTypes[response.asset]}; charset=utf-8`,
15080
+ "content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
15081
+ }, { head });
15082
+ return;
15083
+ }
15084
+ send(res, response.status, JSON.stringify(response.body), {
15085
+ // Rejections may precede body consumption (for example Content-Length
15086
+ // above the limit). Do not reuse a socket containing unread body bytes.
15087
+ ...response.status >= 400 ? { connection: "close" } : {},
15088
+ "content-type": "application/json; charset=utf-8",
15089
+ "cross-origin-resource-policy": "same-origin",
15090
+ "content-security-policy": "default-src 'none'; frame-ancestors 'none'",
15091
+ "x-frame-options": "DENY"
15092
+ }, { head });
15093
+ return;
15094
+ }
15045
15095
  if (segments.length < 3 || segments[0] !== "r" || !safeSessionId(segments[1])) {
15046
15096
  notFound(res, { head });
15047
15097
  return;
@@ -15200,6 +15250,32 @@ function createArtifactReviewServer({
15200
15250
  controlToken,
15201
15251
  instanceId,
15202
15252
  sessionCount: () => sessions.size,
15253
+ /** Trusted local authority only; this operation has no HTTP control route. */
15254
+ registerOwnerSession({ handleRequest } = {}) {
15255
+ if (draining || closePromise || typeof handleRequest !== "function") {
15256
+ throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Local owner session cannot be registered.");
15257
+ }
15258
+ const id4 = mintCapabilityToken({ bytes: SESSION_ID_BYTES });
15259
+ const owner = {
15260
+ id: id4,
15261
+ capability: mintCapabilityToken({ bytes: SESSION_TOKEN_BYTES }),
15262
+ recoveryScope: `diagram-owner_${id4}`,
15263
+ handleRequest,
15264
+ pending: /* @__PURE__ */ new Set()
15265
+ };
15266
+ ownerSessions.set(id4, owner);
15267
+ return Object.freeze({
15268
+ sessionId: id4,
15269
+ capability: owner.capability,
15270
+ recoveryScope: owner.recoveryScope,
15271
+ path: `/o/${id4}/${owner.capability}/`,
15272
+ async close() {
15273
+ ownerSessions.delete(id4);
15274
+ await Promise.allSettled([...owner.pending]);
15275
+ if (idle()) scheduleEmpty();
15276
+ }
15277
+ });
15278
+ },
15203
15279
  isIdle: idle,
15204
15280
  accepting: () => !draining && !closePromise,
15205
15281
  beginCloseIfIdle() {
@@ -15224,6 +15300,9 @@ function createArtifactReviewServer({
15224
15300
  emptyTimer = null;
15225
15301
  closePromise = (async () => {
15226
15302
  sessions.clear();
15303
+ const pendingOwners = [...ownerSessions.values()].flatMap((owner) => [...owner.pending]);
15304
+ ownerSessions.clear();
15305
+ await Promise.allSettled(pendingOwners);
15227
15306
  await closeHttpServer(server);
15228
15307
  })();
15229
15308
  return closePromise;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openplanr/protocol",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Canonical OpenPlanr schemas, registries, typed errors, and portable contract validation",
6
6
  "license": "MIT",
@@ -83,6 +83,10 @@
83
83
  "./enterprise-contracts": {
84
84
  "types": "./src/enterprise-contracts.d.mts",
85
85
  "import": "./src/enterprise-contracts.mjs"
86
+ },
87
+ "./diagram-authoring-contracts": {
88
+ "types": "./src/diagram-authoring-contracts.d.mts",
89
+ "import": "./src/diagram-authoring-contracts.mjs"
86
90
  }
87
91
  },
88
92
  "files": [
@@ -4,14 +4,14 @@
4
4
  "name": "AsemDevs"
5
5
  },
6
6
  "metadata": {
7
- "version": "2.6.1",
7
+ "version": "2.6.3",
8
8
  "description": "Generated local OpenPlanr package."
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "planr",
13
13
  "source": "./openplanr",
14
- "version": "2.6.1",
14
+ "version": "2.6.3",
15
15
  "description": "Host-native OpenPlanr skills for OpenAI coding agents.",
16
16
  "strict": true
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "planr",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "Host-native OpenPlanr planning, delivery, review, design, and operating skills.",
5
5
  "author": {
6
6
  "name": "AsemDevs"
@@ -8,7 +8,7 @@
8
8
  "files": [
9
9
  {
10
10
  "path": ".codex-plugin/plugin.json",
11
- "digest": "sha256:b8777f1c132d42a4226884e4cadd407f51185d756ef6bad7cce68565bac02ea9"
11
+ "digest": "sha256:56ba6c4519d4d20524b8a874342e21cadb55f5b87c7b5514540e11a104488965"
12
12
  },
13
13
  {
14
14
  "path": "skills/artifact/agents/openai.yaml",
@@ -328,7 +328,7 @@
328
328
  },
329
329
  {
330
330
  "path": "skills/design-loop/scripts/design.mjs",
331
- "digest": "sha256:3fec9a1d47bc41c0caf4909a2c4b9d00205da630c015a46f6921caf2a4d7a2d2"
331
+ "digest": "sha256:7a554ec4a32ea6caa6f3bb9f9e9059d586527afd73e33feb708739b0bb75c627"
332
332
  },
333
333
  {
334
334
  "path": "skills/design-loop/scripts/runtime/notices/entities.txt",
@@ -376,7 +376,7 @@
376
376
  },
377
377
  {
378
378
  "path": "skills/design-loop/scripts/runtime/packages/protocol/package.json",
379
- "digest": "sha256:005fd84b459d0673203781da7693ffb28b379ef73096213fd6df5c2606617730"
379
+ "digest": "sha256:6e51f9d61b978532cd129f442a4e88f9f3900e58a7239427ec264c34a1337ac3"
380
380
  },
381
381
  {
382
382
  "path": "skills/design-loop/scripts/runtime/packages/protocol/registry/artifact-theme.json",
@@ -520,7 +520,7 @@
520
520
  },
521
521
  {
522
522
  "path": "skills/design-review/scripts/design.mjs",
523
- "digest": "sha256:3fec9a1d47bc41c0caf4909a2c4b9d00205da630c015a46f6921caf2a4d7a2d2"
523
+ "digest": "sha256:7a554ec4a32ea6caa6f3bb9f9e9059d586527afd73e33feb708739b0bb75c627"
524
524
  },
525
525
  {
526
526
  "path": "skills/design-review/scripts/runtime/notices/entities.txt",
@@ -568,7 +568,7 @@
568
568
  },
569
569
  {
570
570
  "path": "skills/design-review/scripts/runtime/packages/protocol/package.json",
571
- "digest": "sha256:005fd84b459d0673203781da7693ffb28b379ef73096213fd6df5c2606617730"
571
+ "digest": "sha256:6e51f9d61b978532cd129f442a4e88f9f3900e58a7239427ec264c34a1337ac3"
572
572
  },
573
573
  {
574
574
  "path": "skills/design-review/scripts/runtime/packages/protocol/registry/artifact-theme.json",
@@ -712,7 +712,7 @@
712
712
  },
713
713
  {
714
714
  "path": "skills/design/scripts/design.mjs",
715
- "digest": "sha256:3fec9a1d47bc41c0caf4909a2c4b9d00205da630c015a46f6921caf2a4d7a2d2"
715
+ "digest": "sha256:7a554ec4a32ea6caa6f3bb9f9e9059d586527afd73e33feb708739b0bb75c627"
716
716
  },
717
717
  {
718
718
  "path": "skills/design/scripts/runtime/notices/entities.txt",
@@ -760,7 +760,7 @@
760
760
  },
761
761
  {
762
762
  "path": "skills/design/scripts/runtime/packages/protocol/package.json",
763
- "digest": "sha256:005fd84b459d0673203781da7693ffb28b379ef73096213fd6df5c2606617730"
763
+ "digest": "sha256:6e51f9d61b978532cd129f442a4e88f9f3900e58a7239427ec264c34a1337ac3"
764
764
  },
765
765
  {
766
766
  "path": "skills/design/scripts/runtime/packages/protocol/registry/artifact-theme.json",
@@ -860,7 +860,7 @@
860
860
  },
861
861
  {
862
862
  "path": "skills/diagram/references/diagram-fidelity.md",
863
- "digest": "sha256:3eadcce2990f6d74a0eb96e26b78ecd7e493322ae6a8aee3cdcecc66ca3d75bd"
863
+ "digest": "sha256:25294a35cd4cef3744023d0fcf26bb57585f2fa544f712e54f1dd14d2c773522"
864
864
  },
865
865
  {
866
866
  "path": "skills/diagram/references/diagram-intent-to-ir.md",
@@ -992,7 +992,7 @@
992
992
  },
993
993
  {
994
994
  "path": "skills/plan/scripts/design.mjs",
995
- "digest": "sha256:3fec9a1d47bc41c0caf4909a2c4b9d00205da630c015a46f6921caf2a4d7a2d2"
995
+ "digest": "sha256:7a554ec4a32ea6caa6f3bb9f9e9059d586527afd73e33feb708739b0bb75c627"
996
996
  },
997
997
  {
998
998
  "path": "skills/plan/scripts/planning-ids.mjs",
@@ -1044,7 +1044,7 @@
1044
1044
  },
1045
1045
  {
1046
1046
  "path": "skills/plan/scripts/runtime/packages/protocol/package.json",
1047
- "digest": "sha256:005fd84b459d0673203781da7693ffb28b379ef73096213fd6df5c2606617730"
1047
+ "digest": "sha256:6e51f9d61b978532cd129f442a4e88f9f3900e58a7239427ec264c34a1337ac3"
1048
1048
  },
1049
1049
  {
1050
1050
  "path": "skills/plan/scripts/runtime/packages/protocol/registry/artifact-theme.json",
@@ -1259,5 +1259,5 @@
1259
1259
  "digest": "sha256:53698324b62f43b672ce8fe1654c526c96fb202e313442f09c6fc1e72268677e"
1260
1260
  }
1261
1261
  ],
1262
- "contentDigest": "sha256:9eef83836e046acc0f58f291da7f3ab9dc8ffe7771c561945976554293b5ad49"
1262
+ "contentDigest": "sha256:f0fccaf05277a2ba6204f6eac657d2ed8274d83d4e390fe52c85da333503f74f"
1263
1263
  }
@@ -14910,6 +14910,7 @@ function createArtifactReviewServer({
14910
14910
  throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Artifact review instance id is invalid.");
14911
14911
  }
14912
14912
  const sessions = /* @__PURE__ */ new Map();
14913
+ const ownerSessions = /* @__PURE__ */ new Map();
14913
14914
  let port = null;
14914
14915
  let closePromise = null;
14915
14916
  let pendingRegistrations = 0;
@@ -14917,7 +14918,7 @@ function createArtifactReviewServer({
14917
14918
  let draining = false;
14918
14919
  let emptyTimer = null;
14919
14920
  const stageRuntime = () => readFileSync11(STAGE_RUNTIME_PATH, "utf8");
14920
- const idle = () => sessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
+ const idle = () => sessions.size === 0 && ownerSessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
14922
  const scheduleEmpty = () => {
14922
14923
  if (emptyTimer || draining || typeof onEmpty !== "function") return;
14923
14924
  emptyTimer = setTimeout(async () => {
@@ -15042,6 +15043,55 @@ function createArtifactReviewServer({
15042
15043
  notFound(res, { head });
15043
15044
  return;
15044
15045
  }
15046
+ if (segments[0] === "o") {
15047
+ const owner = safeSessionId(segments[1]) && ownerSessions.get(segments[1]);
15048
+ if (draining || !sessionMatches(owner, segments[2])) {
15049
+ notFound(res, { head });
15050
+ return;
15051
+ }
15052
+ if (segments.length === 3 && !trailingSlash && ["GET", "HEAD"].includes(req.method)) {
15053
+ send(res, 308, "", { location: `/o/${owner.id}/${owner.capability}/` }, { head });
15054
+ return;
15055
+ }
15056
+ const request2 = Promise.resolve().then(() => owner.handleRequest({
15057
+ req,
15058
+ segments: segments.slice(3),
15059
+ head,
15060
+ origin: `http://${LOOPBACK_HOST}:${port}`,
15061
+ recoveryScope: owner.recoveryScope
15062
+ }));
15063
+ owner.pending.add(request2);
15064
+ let response;
15065
+ try {
15066
+ response = await request2;
15067
+ } finally {
15068
+ owner.pending.delete(request2);
15069
+ }
15070
+ if (!response) {
15071
+ notFound(res, { head });
15072
+ return;
15073
+ }
15074
+ if (response.kind === "asset") {
15075
+ const mediaTypes = { document: "text/html", runtime: "text/javascript", stylesheet: "text/css" };
15076
+ if (!Object.hasOwn(mediaTypes, response.asset) || typeof response.body !== "string") throw new Error("Invalid owner asset response.");
15077
+ send(res, response.status, response.body, {
15078
+ ...parentHeaders(),
15079
+ "content-type": `${mediaTypes[response.asset]}; charset=utf-8`,
15080
+ "content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
15081
+ }, { head });
15082
+ return;
15083
+ }
15084
+ send(res, response.status, JSON.stringify(response.body), {
15085
+ // Rejections may precede body consumption (for example Content-Length
15086
+ // above the limit). Do not reuse a socket containing unread body bytes.
15087
+ ...response.status >= 400 ? { connection: "close" } : {},
15088
+ "content-type": "application/json; charset=utf-8",
15089
+ "cross-origin-resource-policy": "same-origin",
15090
+ "content-security-policy": "default-src 'none'; frame-ancestors 'none'",
15091
+ "x-frame-options": "DENY"
15092
+ }, { head });
15093
+ return;
15094
+ }
15045
15095
  if (segments.length < 3 || segments[0] !== "r" || !safeSessionId(segments[1])) {
15046
15096
  notFound(res, { head });
15047
15097
  return;
@@ -15200,6 +15250,32 @@ function createArtifactReviewServer({
15200
15250
  controlToken,
15201
15251
  instanceId,
15202
15252
  sessionCount: () => sessions.size,
15253
+ /** Trusted local authority only; this operation has no HTTP control route. */
15254
+ registerOwnerSession({ handleRequest } = {}) {
15255
+ if (draining || closePromise || typeof handleRequest !== "function") {
15256
+ throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Local owner session cannot be registered.");
15257
+ }
15258
+ const id4 = mintCapabilityToken({ bytes: SESSION_ID_BYTES });
15259
+ const owner = {
15260
+ id: id4,
15261
+ capability: mintCapabilityToken({ bytes: SESSION_TOKEN_BYTES }),
15262
+ recoveryScope: `diagram-owner_${id4}`,
15263
+ handleRequest,
15264
+ pending: /* @__PURE__ */ new Set()
15265
+ };
15266
+ ownerSessions.set(id4, owner);
15267
+ return Object.freeze({
15268
+ sessionId: id4,
15269
+ capability: owner.capability,
15270
+ recoveryScope: owner.recoveryScope,
15271
+ path: `/o/${id4}/${owner.capability}/`,
15272
+ async close() {
15273
+ ownerSessions.delete(id4);
15274
+ await Promise.allSettled([...owner.pending]);
15275
+ if (idle()) scheduleEmpty();
15276
+ }
15277
+ });
15278
+ },
15203
15279
  isIdle: idle,
15204
15280
  accepting: () => !draining && !closePromise,
15205
15281
  beginCloseIfIdle() {
@@ -15224,6 +15300,9 @@ function createArtifactReviewServer({
15224
15300
  emptyTimer = null;
15225
15301
  closePromise = (async () => {
15226
15302
  sessions.clear();
15303
+ const pendingOwners = [...ownerSessions.values()].flatMap((owner) => [...owner.pending]);
15304
+ ownerSessions.clear();
15305
+ await Promise.allSettled(pendingOwners);
15227
15306
  await closeHttpServer(server);
15228
15307
  })();
15229
15308
  return closePromise;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openplanr/protocol",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Canonical OpenPlanr schemas, registries, typed errors, and portable contract validation",
6
6
  "license": "MIT",
@@ -83,6 +83,10 @@
83
83
  "./enterprise-contracts": {
84
84
  "types": "./src/enterprise-contracts.d.mts",
85
85
  "import": "./src/enterprise-contracts.mjs"
86
+ },
87
+ "./diagram-authoring-contracts": {
88
+ "types": "./src/diagram-authoring-contracts.d.mts",
89
+ "import": "./src/diagram-authoring-contracts.mjs"
86
90
  }
87
91
  },
88
92
  "files": [
@@ -14910,6 +14910,7 @@ function createArtifactReviewServer({
14910
14910
  throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Artifact review instance id is invalid.");
14911
14911
  }
14912
14912
  const sessions = /* @__PURE__ */ new Map();
14913
+ const ownerSessions = /* @__PURE__ */ new Map();
14913
14914
  let port = null;
14914
14915
  let closePromise = null;
14915
14916
  let pendingRegistrations = 0;
@@ -14917,7 +14918,7 @@ function createArtifactReviewServer({
14917
14918
  let draining = false;
14918
14919
  let emptyTimer = null;
14919
14920
  const stageRuntime = () => readFileSync11(STAGE_RUNTIME_PATH, "utf8");
14920
- const idle = () => sessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
+ const idle = () => sessions.size === 0 && ownerSessions.size === 0 && pendingRegistrations === 0 && activeRequests === 0;
14921
14922
  const scheduleEmpty = () => {
14922
14923
  if (emptyTimer || draining || typeof onEmpty !== "function") return;
14923
14924
  emptyTimer = setTimeout(async () => {
@@ -15042,6 +15043,55 @@ function createArtifactReviewServer({
15042
15043
  notFound(res, { head });
15043
15044
  return;
15044
15045
  }
15046
+ if (segments[0] === "o") {
15047
+ const owner = safeSessionId(segments[1]) && ownerSessions.get(segments[1]);
15048
+ if (draining || !sessionMatches(owner, segments[2])) {
15049
+ notFound(res, { head });
15050
+ return;
15051
+ }
15052
+ if (segments.length === 3 && !trailingSlash && ["GET", "HEAD"].includes(req.method)) {
15053
+ send(res, 308, "", { location: `/o/${owner.id}/${owner.capability}/` }, { head });
15054
+ return;
15055
+ }
15056
+ const request2 = Promise.resolve().then(() => owner.handleRequest({
15057
+ req,
15058
+ segments: segments.slice(3),
15059
+ head,
15060
+ origin: `http://${LOOPBACK_HOST}:${port}`,
15061
+ recoveryScope: owner.recoveryScope
15062
+ }));
15063
+ owner.pending.add(request2);
15064
+ let response;
15065
+ try {
15066
+ response = await request2;
15067
+ } finally {
15068
+ owner.pending.delete(request2);
15069
+ }
15070
+ if (!response) {
15071
+ notFound(res, { head });
15072
+ return;
15073
+ }
15074
+ if (response.kind === "asset") {
15075
+ const mediaTypes = { document: "text/html", runtime: "text/javascript", stylesheet: "text/css" };
15076
+ if (!Object.hasOwn(mediaTypes, response.asset) || typeof response.body !== "string") throw new Error("Invalid owner asset response.");
15077
+ send(res, response.status, response.body, {
15078
+ ...parentHeaders(),
15079
+ "content-type": `${mediaTypes[response.asset]}; charset=utf-8`,
15080
+ "content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
15081
+ }, { head });
15082
+ return;
15083
+ }
15084
+ send(res, response.status, JSON.stringify(response.body), {
15085
+ // Rejections may precede body consumption (for example Content-Length
15086
+ // above the limit). Do not reuse a socket containing unread body bytes.
15087
+ ...response.status >= 400 ? { connection: "close" } : {},
15088
+ "content-type": "application/json; charset=utf-8",
15089
+ "cross-origin-resource-policy": "same-origin",
15090
+ "content-security-policy": "default-src 'none'; frame-ancestors 'none'",
15091
+ "x-frame-options": "DENY"
15092
+ }, { head });
15093
+ return;
15094
+ }
15045
15095
  if (segments.length < 3 || segments[0] !== "r" || !safeSessionId(segments[1])) {
15046
15096
  notFound(res, { head });
15047
15097
  return;
@@ -15200,6 +15250,32 @@ function createArtifactReviewServer({
15200
15250
  controlToken,
15201
15251
  instanceId,
15202
15252
  sessionCount: () => sessions.size,
15253
+ /** Trusted local authority only; this operation has no HTTP control route. */
15254
+ registerOwnerSession({ handleRequest } = {}) {
15255
+ if (draining || closePromise || typeof handleRequest !== "function") {
15256
+ throw artifactError(ARTIFACT_ERROR_CODES.LOOPBACK_STATE, "Local owner session cannot be registered.");
15257
+ }
15258
+ const id4 = mintCapabilityToken({ bytes: SESSION_ID_BYTES });
15259
+ const owner = {
15260
+ id: id4,
15261
+ capability: mintCapabilityToken({ bytes: SESSION_TOKEN_BYTES }),
15262
+ recoveryScope: `diagram-owner_${id4}`,
15263
+ handleRequest,
15264
+ pending: /* @__PURE__ */ new Set()
15265
+ };
15266
+ ownerSessions.set(id4, owner);
15267
+ return Object.freeze({
15268
+ sessionId: id4,
15269
+ capability: owner.capability,
15270
+ recoveryScope: owner.recoveryScope,
15271
+ path: `/o/${id4}/${owner.capability}/`,
15272
+ async close() {
15273
+ ownerSessions.delete(id4);
15274
+ await Promise.allSettled([...owner.pending]);
15275
+ if (idle()) scheduleEmpty();
15276
+ }
15277
+ });
15278
+ },
15203
15279
  isIdle: idle,
15204
15280
  accepting: () => !draining && !closePromise,
15205
15281
  beginCloseIfIdle() {
@@ -15224,6 +15300,9 @@ function createArtifactReviewServer({
15224
15300
  emptyTimer = null;
15225
15301
  closePromise = (async () => {
15226
15302
  sessions.clear();
15303
+ const pendingOwners = [...ownerSessions.values()].flatMap((owner) => [...owner.pending]);
15304
+ ownerSessions.clear();
15305
+ await Promise.allSettled(pendingOwners);
15227
15306
  await closeHttpServer(server);
15228
15307
  })();
15229
15308
  return closePromise;