@skillcdn/console 0.4.2 → 0.4.4

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 (243) hide show
  1. package/README.md +4 -4
  2. package/dist/api.d.ts +3 -3
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +3 -3
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +120 -33
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts +4 -2
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +38 -10
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +22 -15
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/cli/plugin-check.d.ts +10 -1
  17. package/dist/cli/plugin-check.d.ts.map +1 -1
  18. package/dist/cli/plugin-check.js +45 -4
  19. package/dist/cli/plugin-check.js.map +1 -1
  20. package/dist/cli/plugin-cli.d.ts +1 -1
  21. package/dist/cli/plugin-cli.d.ts.map +1 -1
  22. package/dist/cli/plugin-cli.js +80 -5
  23. package/dist/cli/plugin-cli.js.map +1 -1
  24. package/dist/cli/plugin-init.d.ts +19 -0
  25. package/dist/cli/plugin-init.d.ts.map +1 -1
  26. package/dist/cli/plugin-init.js +141 -3
  27. package/dist/cli/plugin-init.js.map +1 -1
  28. package/dist/client.d.ts +7 -2
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +18 -5
  31. package/dist/client.js.map +1 -1
  32. package/dist/compat.js +1 -1
  33. package/dist/components/board.d.ts +8 -0
  34. package/dist/components/board.d.ts.map +1 -1
  35. package/dist/components/board.js +3 -3
  36. package/dist/components/board.js.map +1 -1
  37. package/dist/components/comments.d.ts.map +1 -1
  38. package/dist/components/comments.js +1 -1
  39. package/dist/components/comments.js.map +1 -1
  40. package/dist/components/decision-form.d.ts +8 -1
  41. package/dist/components/decision-form.d.ts.map +1 -1
  42. package/dist/components/decision-form.js +57 -7
  43. package/dist/components/decision-form.js.map +1 -1
  44. package/dist/components/decision-list.d.ts +9 -1
  45. package/dist/components/decision-list.d.ts.map +1 -1
  46. package/dist/components/decision-list.js +53 -7
  47. package/dist/components/decision-list.js.map +1 -1
  48. package/dist/components/event-feed.d.ts.map +1 -1
  49. package/dist/components/event-feed.js +7 -1
  50. package/dist/components/event-feed.js.map +1 -1
  51. package/dist/components/goals.d.ts.map +1 -1
  52. package/dist/components/goals.js +2 -2
  53. package/dist/components/goals.js.map +1 -1
  54. package/dist/components/home.d.ts +35 -0
  55. package/dist/components/home.d.ts.map +1 -0
  56. package/dist/components/home.js +326 -0
  57. package/dist/components/home.js.map +1 -0
  58. package/dist/components/my-decisions.d.ts +2 -0
  59. package/dist/components/my-decisions.d.ts.map +1 -1
  60. package/dist/components/my-decisions.js +2 -2
  61. package/dist/components/my-decisions.js.map +1 -1
  62. package/dist/components/pack-connect.d.ts.map +1 -1
  63. package/dist/components/pack-connect.js +5 -4
  64. package/dist/components/pack-connect.js.map +1 -1
  65. package/dist/components/people.d.ts +2 -0
  66. package/dist/components/people.d.ts.map +1 -1
  67. package/dist/components/people.js +15 -2
  68. package/dist/components/people.js.map +1 -1
  69. package/dist/components/plugin-frame.d.ts +3 -12
  70. package/dist/components/plugin-frame.d.ts.map +1 -1
  71. package/dist/components/plugin-frame.js +53 -20
  72. package/dist/components/plugin-frame.js.map +1 -1
  73. package/dist/components/plugin-settings.d.ts.map +1 -1
  74. package/dist/components/plugin-settings.js +151 -26
  75. package/dist/components/plugin-settings.js.map +1 -1
  76. package/dist/components/projects.d.ts +0 -8
  77. package/dist/components/projects.d.ts.map +1 -1
  78. package/dist/components/projects.js +1 -13
  79. package/dist/components/projects.js.map +1 -1
  80. package/dist/components/task-view.d.ts +2 -1
  81. package/dist/components/task-view.d.ts.map +1 -1
  82. package/dist/components/task-view.js +2 -1
  83. package/dist/components/task-view.js.map +1 -1
  84. package/dist/components/ui.d.ts.map +1 -1
  85. package/dist/components/ui.js +5 -2
  86. package/dist/components/ui.js.map +1 -1
  87. package/dist/connect-page.d.ts.map +1 -1
  88. package/dist/connect-page.js +5 -6
  89. package/dist/connect-page.js.map +1 -1
  90. package/dist/console.css +764 -192
  91. package/dist/console.d.ts +10 -3
  92. package/dist/console.d.ts.map +1 -1
  93. package/dist/console.js +49 -28
  94. package/dist/console.js.map +1 -1
  95. package/dist/data.d.ts +6 -3
  96. package/dist/data.d.ts.map +1 -1
  97. package/dist/data.js +31 -7
  98. package/dist/data.js.map +1 -1
  99. package/dist/docs-page.d.ts.map +1 -1
  100. package/dist/docs-page.js +6 -5
  101. package/dist/docs-page.js.map +1 -1
  102. package/dist/home-tiles.d.ts +92 -0
  103. package/dist/home-tiles.d.ts.map +1 -0
  104. package/dist/home-tiles.js +294 -0
  105. package/dist/home-tiles.js.map +1 -0
  106. package/dist/i18n/en.d.ts +180 -0
  107. package/dist/i18n/en.d.ts.map +1 -1
  108. package/dist/i18n/en.js +181 -1
  109. package/dist/i18n/en.js.map +1 -1
  110. package/dist/i18n/index.d.ts +4 -2
  111. package/dist/i18n/index.d.ts.map +1 -1
  112. package/dist/i18n/index.js +10 -11
  113. package/dist/i18n/index.js.map +1 -1
  114. package/dist/i18n/ko.d.ts.map +1 -1
  115. package/dist/i18n/ko.js +176 -3
  116. package/dist/i18n/ko.js.map +1 -1
  117. package/dist/index.d.ts +4 -3
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +4 -3
  120. package/dist/index.js.map +1 -1
  121. package/dist/limits.d.ts +8 -0
  122. package/dist/limits.d.ts.map +1 -1
  123. package/dist/limits.js +8 -0
  124. package/dist/limits.js.map +1 -1
  125. package/dist/plugin/bridge.d.ts +44 -3
  126. package/dist/plugin/bridge.d.ts.map +1 -1
  127. package/dist/plugin/bridge.js +26 -0
  128. package/dist/plugin/bridge.js.map +1 -1
  129. package/dist/plugin/capabilities.d.ts +7 -1
  130. package/dist/plugin/capabilities.d.ts.map +1 -1
  131. package/dist/plugin/capabilities.js +14 -1
  132. package/dist/plugin/capabilities.js.map +1 -1
  133. package/dist/plugin/context.d.ts +24 -1
  134. package/dist/plugin/context.d.ts.map +1 -1
  135. package/dist/plugin/define.d.ts +1 -0
  136. package/dist/plugin/define.d.ts.map +1 -1
  137. package/dist/plugin/define.js +1 -1
  138. package/dist/plugin/define.js.map +1 -1
  139. package/dist/plugin/limits.d.ts +5 -2
  140. package/dist/plugin/limits.d.ts.map +1 -1
  141. package/dist/plugin/limits.js +5 -2
  142. package/dist/plugin/limits.js.map +1 -1
  143. package/dist/plugin/manifest.d.ts +83 -6
  144. package/dist/plugin/manifest.d.ts.map +1 -1
  145. package/dist/plugin/manifest.js +111 -7
  146. package/dist/plugin/manifest.js.map +1 -1
  147. package/dist/plugin/rest-schemas.d.ts +76 -0
  148. package/dist/plugin/rest-schemas.d.ts.map +1 -1
  149. package/dist/plugin/rest-schemas.js +23 -2
  150. package/dist/plugin/rest-schemas.js.map +1 -1
  151. package/dist/plugin/sandbox.d.ts +4 -3
  152. package/dist/plugin/sandbox.d.ts.map +1 -1
  153. package/dist/plugin/sandbox.js +33 -6
  154. package/dist/plugin/sandbox.js.map +1 -1
  155. package/dist/plugin/semver.d.ts +26 -0
  156. package/dist/plugin/semver.d.ts.map +1 -0
  157. package/dist/plugin/semver.js +76 -0
  158. package/dist/plugin/semver.js.map +1 -0
  159. package/dist/plugin/testing.d.ts.map +1 -1
  160. package/dist/plugin/testing.js +2 -1
  161. package/dist/plugin/testing.js.map +1 -1
  162. package/dist/plugin/web.d.ts +10 -1
  163. package/dist/plugin/web.d.ts.map +1 -1
  164. package/dist/plugin/web.js +34 -0
  165. package/dist/plugin/web.js.map +1 -1
  166. package/dist/plugin.d.ts +3 -2
  167. package/dist/plugin.d.ts.map +1 -1
  168. package/dist/plugin.js +3 -2
  169. package/dist/plugin.js.map +1 -1
  170. package/dist/rules.d.ts +2 -2
  171. package/dist/rules.js +3 -3
  172. package/dist/schemas.d.ts +574 -11
  173. package/dist/schemas.d.ts.map +1 -1
  174. package/dist/schemas.js +154 -25
  175. package/dist/schemas.js.map +1 -1
  176. package/dist/use-action.d.ts +8 -2
  177. package/dist/use-action.d.ts.map +1 -1
  178. package/dist/use-action.js +13 -6
  179. package/dist/use-action.js.map +1 -1
  180. package/dist/vocabulary.d.ts +3 -1
  181. package/dist/vocabulary.d.ts.map +1 -1
  182. package/dist/vocabulary.js +3 -0
  183. package/dist/vocabulary.js.map +1 -1
  184. package/docs/specs/cli.md +4 -3
  185. package/docs/specs/plugins.md +49 -35
  186. package/docs/specs/rest.md +20 -18
  187. package/package.json +7 -7
  188. package/skills/building-a-plugin/SKILL.md +10 -9
  189. package/skills/planning-the-board/references/setting-up.md +2 -1
  190. package/skills/working-the-board/SKILL.md +3 -3
  191. package/src/api.ts +10 -0
  192. package/src/cli/cli.ts +162 -39
  193. package/src/cli/format.ts +54 -10
  194. package/src/cli/help.ts +23 -14
  195. package/src/cli/plugin-check.ts +68 -2
  196. package/src/cli/plugin-cli.ts +104 -6
  197. package/src/cli/plugin-init.ts +178 -3
  198. package/src/client.ts +36 -9
  199. package/src/compat.ts +1 -1
  200. package/src/components/board.tsx +3 -3
  201. package/src/components/comments.tsx +1 -0
  202. package/src/components/decision-form.tsx +166 -29
  203. package/src/components/decision-list.tsx +116 -15
  204. package/src/components/event-feed.tsx +7 -1
  205. package/src/components/goals.tsx +4 -1
  206. package/src/components/home.tsx +615 -0
  207. package/src/components/my-decisions.tsx +4 -1
  208. package/src/components/pack-connect.tsx +5 -4
  209. package/src/components/people.tsx +136 -54
  210. package/src/components/plugin-frame.tsx +62 -63
  211. package/src/components/plugin-settings.tsx +428 -87
  212. package/src/components/projects.tsx +0 -55
  213. package/src/components/task-view.tsx +9 -1
  214. package/src/components/ui.tsx +6 -2
  215. package/src/connect-page.tsx +5 -6
  216. package/src/console.tsx +98 -42
  217. package/src/data.ts +41 -11
  218. package/src/docs-page.tsx +6 -5
  219. package/src/home-tiles.ts +438 -0
  220. package/src/i18n/en.ts +185 -0
  221. package/src/i18n/index.ts +17 -14
  222. package/src/i18n/ko.ts +181 -3
  223. package/src/index.ts +7 -5
  224. package/src/limits.ts +8 -0
  225. package/src/plugin/bridge.ts +77 -1
  226. package/src/plugin/capabilities.ts +17 -1
  227. package/src/plugin/context.ts +24 -2
  228. package/src/plugin/define.ts +4 -1
  229. package/src/plugin/limits.ts +5 -2
  230. package/src/plugin/manifest.ts +154 -10
  231. package/src/plugin/rest-schemas.ts +27 -1
  232. package/src/plugin/sandbox.ts +36 -8
  233. package/src/plugin/semver.ts +89 -0
  234. package/src/plugin/testing.ts +2 -1
  235. package/src/plugin/web.ts +49 -0
  236. package/src/plugin.ts +18 -0
  237. package/src/rules.ts +3 -3
  238. package/src/schemas.ts +190 -28
  239. package/src/styles/console.css +764 -192
  240. package/src/use-action.ts +16 -7
  241. package/src/vocabulary.ts +4 -0
  242. package/template/plugin/console-plugin.json +3 -1
  243. package/template/plugin/package.json +3 -3
@@ -13,6 +13,15 @@ export const FORBIDDEN_BUILTINS: readonly string[] = [
13
13
  "dgram",
14
14
  "dns",
15
15
  "dns/promises",
16
+ // The same network, under the names Node keeps for compatibility.
17
+ "_http_agent",
18
+ "_http_client",
19
+ "_http_common",
20
+ "_http_incoming",
21
+ "_http_outgoing",
22
+ "_http_server",
23
+ "_tls_common",
24
+ "_tls_wrap",
16
25
  "child_process",
17
26
  "cluster",
18
27
  "worker_threads",
@@ -67,6 +76,15 @@ export interface HardenedThread {
67
76
  readonly removed: readonly string[];
68
77
  }
69
78
 
79
+ /** What a plugin is told of a built-in it may not have, by whichever way it asked. */
80
+ function refusedBuiltin(specifier: string): Error {
81
+ const error = new Error(
82
+ `the host refuses ${specifier}: a plugin reaches the console through ctx alone`,
83
+ );
84
+ (error as { code?: string }).code = "ERR_PLUGIN_FORBIDDEN_BUILTIN";
85
+ return error;
86
+ }
87
+
70
88
  /** The module hook: refuses the forbidden built-ins and lets everything else resolve as it would. */
71
89
  export function forbiddingResolve<C, R>(
72
90
  specifier: string,
@@ -74,19 +92,16 @@ export function forbiddingResolve<C, R>(
74
92
  nextResolve: (specifier: string, context: C) => R,
75
93
  ): R {
76
94
  if (isForbiddenBuiltin(specifier)) {
77
- const error = new Error(
78
- `the host refuses ${specifier}: a plugin reaches the console through ctx alone`,
79
- );
80
- (error as { code?: string }).code = "ERR_PLUGIN_FORBIDDEN_BUILTIN";
81
- throw error;
95
+ throw refusedBuiltin(specifier);
82
96
  }
83
97
  return nextResolve(specifier, context);
84
98
  }
85
99
 
86
100
  /**
87
- * Hardens the current thread: refuses the forbidden built-ins through the module hooks, removes
88
- * the globals, empties the environment, and blocks `process.binding`. Call it in a worker thread
89
- * before importing plugin code; it reaches nothing outside that thread.
101
+ * Hardens the current thread: refuses the forbidden built-ins through the module hooks and through
102
+ * `process.getBuiltinModule`, which goes round them, removes the globals, empties the environment,
103
+ * and blocks `process.binding`. Call it in a worker thread before importing plugin code; it reaches
104
+ * nothing outside that thread.
90
105
  */
91
106
  export function hardenThread(
92
107
  moduleApi: Pick<typeof import("node:module"), "registerHooks">,
@@ -112,6 +127,19 @@ export function hardenThread(
112
127
  for (const name of ["binding", "_linkedBinding", "dlopen"]) {
113
128
  Object.defineProperty(proc, name, { value: refuse, writable: false, configurable: false });
114
129
  }
130
+ const getBuiltinModule = proc.getBuiltinModule;
131
+ if (typeof getBuiltinModule === "function") {
132
+ Object.defineProperty(proc, "getBuiltinModule", {
133
+ value: (specifier: string): unknown => {
134
+ if (isForbiddenBuiltin(specifier)) {
135
+ throw refusedBuiltin(specifier);
136
+ }
137
+ return getBuiltinModule.call(proc, specifier);
138
+ },
139
+ writable: false,
140
+ configurable: false,
141
+ });
142
+ }
115
143
  const env = proc.env as Record<string, unknown> | undefined;
116
144
  if (env !== undefined) {
117
145
  for (const key of Object.keys(env)) {
@@ -0,0 +1,89 @@
1
+ // Versions and ranges as a manifest spells them (docs/specs/plugins.md, "The manifest"): the
2
+ // range of another plugin's version in `requires`, and the range of the console's version in
3
+ // `console`. Read loosely, as npm spells the common ones, with no library: a plugin declares
4
+ // `>=0.4.3 <0.6`, and the console, the authoring command and the tests compare the same way.
5
+
6
+ export type Triple = readonly [number, number, number];
7
+
8
+ /**
9
+ * A version read loosely: `0.4.3`, `v0.4.3`, `0.6` (as `0.6.0`), `1` (as `1.0.0`), a prerelease or
10
+ * a build after the numbers ignored; nothing for anything else.
11
+ */
12
+ export function parseLooseVersion(text: string): Triple | undefined {
13
+ const match = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-+].*)?$/.exec(text.trim());
14
+ return match === null
15
+ ? undefined
16
+ : [Number(match[1]), Number(match[2] ?? "0"), Number(match[3] ?? "0")];
17
+ }
18
+
19
+ export function compareTriples(a: Triple, b: Triple): number {
20
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
21
+ }
22
+
23
+ /**
24
+ * Whether a version is within a range as npm spells the common ones: `*`, exact, `^`, `~`, `>=`,
25
+ * `>`, `<=`, `<`, several with spaces between (all must hold) and alternatives with `||` between
26
+ * (one must hold). A partial version in the range is read as `.0` filled in: `<0.6` is `<0.6.0`.
27
+ */
28
+ export function satisfiesRange(version: string, range: string): boolean {
29
+ const have = parseLooseVersion(version);
30
+ const trimmed = range.trim();
31
+ if (trimmed === "*" || trimmed === "" || trimmed === "x" || trimmed === "latest") {
32
+ return true;
33
+ }
34
+ if (have === undefined) {
35
+ return false;
36
+ }
37
+ return trimmed.split(/\s*\|\|\s*/).some((alternative) =>
38
+ alternative.split(/\s+/).every((part) => {
39
+ const operator = /^(\^|~|>=|<=|>|<|=)?(.*)$/.exec(part);
40
+ const wanted = parseLooseVersion(operator?.[2] ?? "");
41
+ if (wanted === undefined) {
42
+ return false;
43
+ }
44
+ switch (operator?.[1]) {
45
+ case "^":
46
+ return (
47
+ compareTriples(have, wanted) >= 0 &&
48
+ (wanted[0] === 0 ? have[0] === 0 && have[1] === wanted[1] : have[0] === wanted[0])
49
+ );
50
+ case "~":
51
+ return (
52
+ compareTriples(have, wanted) >= 0 && have[0] === wanted[0] && have[1] === wanted[1]
53
+ );
54
+ case ">=":
55
+ return compareTriples(have, wanted) >= 0;
56
+ case ">":
57
+ return compareTriples(have, wanted) > 0;
58
+ case "<=":
59
+ return compareTriples(have, wanted) <= 0;
60
+ case "<":
61
+ return compareTriples(have, wanted) < 0;
62
+ default:
63
+ return compareTriples(have, wanted) === 0;
64
+ }
65
+ }),
66
+ );
67
+ }
68
+
69
+ /**
70
+ * The version a dependency spells, read loosely: `^0.4.3`, `~0.4.3`, `>=0.4.3` and `0.4.3` are
71
+ * all `0.4.3`; `workspace:*`, `*`, `latest` and a tag name none.
72
+ */
73
+ export function versionOfDependency(spec: string): Triple | undefined {
74
+ return parseLooseVersion(spec.trim().replace(/^(?:\^|~|>=|=)\s*/, ""));
75
+ }
76
+
77
+ export function formatTriple(version: Triple): string {
78
+ return `${version[0]}.${version[1]}.${version[2]}`;
79
+ }
80
+
81
+ /**
82
+ * The range of consoles a plugin written against `version` runs on, as `console-plugin init`
83
+ * writes it: from that version on, within its line, since the plugin API moves with the console's
84
+ * line before 1.0 (`>=0.4.3 <0.5.0`), and within its major from 1.0 (`>=1.2.0 <2.0.0`).
85
+ */
86
+ export function lineRangeFrom(version: Triple): string {
87
+ const below: Triple = version[0] === 0 ? [0, version[1] + 1, 0] : [version[0] + 1, 0, 0];
88
+ return `>=${formatTriple(version)} <${formatTriple(below)}`;
89
+ }
@@ -519,7 +519,7 @@ function refusingConsole(given: TestConsoleOptions | undefined): PluginConsole {
519
519
  return {
520
520
  workspace:
521
521
  given?.workspace ?? (async () => forbidden("console.workspace", "console.workspace")),
522
- people: fill("people", ["list", "get"], given?.people),
522
+ people: fill("people", ["list", "get", "granted"], given?.people),
523
523
  projects: fill("projects", ["list", "get", "members"], given?.projects),
524
524
  tasks: fill("tasks", ["list", "get", "create", "update", "comment"], given?.tasks),
525
525
  decisions: fill("decisions", ["list", "get", "ask"], given?.decisions),
@@ -538,6 +538,7 @@ function build(store: Store, options: TestContextOptions, caller: PluginCaller):
538
538
  version: "0.0.0",
539
539
  dataVersion: 1,
540
540
  settings: {},
541
+ capabilities: [],
541
542
  board: null,
542
543
  projects: "all",
543
544
  ...options.plugin,
package/src/plugin/web.ts CHANGED
@@ -11,11 +11,13 @@ import {
11
11
  type BridgeFile,
12
12
  type BridgeInit,
13
13
  type BridgeMethod,
14
+ type BridgePicture,
14
15
  type BridgeToPage,
15
16
  isBridgePath,
16
17
  isBridgeToFrame,
17
18
  MAX_BRIDGE_TITLE_LENGTH,
18
19
  PLUGIN_BRIDGE_VERSION,
20
+ type PluginWidgetSizeName,
19
21
  } from "./bridge.js";
20
22
  import type { PluginRecord, RecordPage, RecordVersion } from "./context.js";
21
23
 
@@ -111,6 +113,13 @@ export interface PluginFrame {
111
113
  type: string,
112
114
  ): Promise<{ readonly handle: string }>;
113
115
  };
116
+ readonly people: {
117
+ /**
118
+ * An address to put in an image, of the person of that id (ADR-0063), with `people:read`; or
119
+ * `null` for a person with no picture to show. Kept for the life of the frame.
120
+ */
121
+ picture(id: string): Promise<string | null>;
122
+ };
114
123
  /** The console's page follows a path of the console's in place; a URL is refused. */
115
124
  navigate(path: string): void;
116
125
  /** The console's page downloads a file of this plugin. */
@@ -125,12 +134,21 @@ export interface PluginFrame {
125
134
  onTheme(handler: (theme: Readonly<Record<string, string>>) => void): () => void;
126
135
  /** The console navigated within this page's address. */
127
136
  onPath(handler: (path: string) => void): () => void;
137
+ /** The person gave the widget another size (ADR-0059); `context.size` was the first. */
138
+ onSize(handler: (size: PluginWidgetSizeName) => void): () => void;
128
139
  /** Stops listening to the console's page. */
129
140
  close(): void;
130
141
  }
131
142
 
132
143
  const DEFAULT_CONNECT_MS = 10_000;
133
144
 
145
+ /** What a page is told for a person with no picture to show, which is no failure of the page's. */
146
+ const NO_PICTURE: ReadonlySet<string> = new Set([
147
+ "plugin.not_found",
148
+ "picture.refused",
149
+ "picture.unavailable",
150
+ ]);
151
+
134
152
  function globalWindow(): FrameWindow {
135
153
  const candidate = (globalThis as { window?: FrameWindow }).window;
136
154
  if (candidate === undefined) {
@@ -219,7 +237,9 @@ export function connect(options: ConnectOptions = {}): Promise<PluginFrame> {
219
237
  const eventHandlers = new Set<(kind: BridgeEventKind, data: unknown) => void>();
220
238
  const themeHandlers = new Set<(theme: Readonly<Record<string, string>>) => void>();
221
239
  const pathHandlers = new Set<(path: string) => void>();
240
+ const sizeHandlers = new Set<(size: PluginWidgetSizeName) => void>();
222
241
  const subscribed = new Set<BridgeEventKind>();
242
+ const pictures = new Map<string, Promise<string | null>>();
223
243
  let counter = 0;
224
244
  let stopHeight: (() => void) | undefined;
225
245
 
@@ -301,6 +321,26 @@ export function connect(options: ConnectOptions = {}): Promise<PluginFrame> {
301
321
  }>;
302
322
  },
303
323
  },
324
+ people: {
325
+ picture: (id) => {
326
+ const kept = pictures.get(id);
327
+ if (kept !== undefined) {
328
+ return kept;
329
+ }
330
+ const asked = (call("people.picture", { id }) as Promise<BridgePicture>).then(
331
+ ({ type, bytes }) => URL.createObjectURL(new Blob([bytes], { type })),
332
+ (error: unknown) => {
333
+ if (error instanceof PluginBridgeError && NO_PICTURE.has(error.code)) {
334
+ return null;
335
+ }
336
+ pictures.delete(id);
337
+ throw error;
338
+ },
339
+ );
340
+ pictures.set(id, asked);
341
+ return asked;
342
+ },
343
+ },
304
344
  navigate: (path) => {
305
345
  if (!isBridgePath(path)) {
306
346
  throw new PluginBridgeError({
@@ -338,6 +378,10 @@ export function connect(options: ConnectOptions = {}): Promise<PluginFrame> {
338
378
  pathHandlers.add(handler);
339
379
  return () => pathHandlers.delete(handler);
340
380
  },
381
+ onSize: (handler) => {
382
+ sizeHandlers.add(handler);
383
+ return () => sizeHandlers.delete(handler);
384
+ },
341
385
  close: () => {
342
386
  win.removeEventListener("message", listener);
343
387
  stopHeight?.();
@@ -393,6 +437,11 @@ export function connect(options: ConnectOptions = {}): Promise<PluginFrame> {
393
437
  handler(message.path);
394
438
  }
395
439
  return;
440
+ case "size":
441
+ for (const handler of sizeHandlers) {
442
+ handler(message.size);
443
+ }
444
+ return;
396
445
  default:
397
446
  return;
398
447
  }
package/src/plugin.ts CHANGED
@@ -29,6 +29,7 @@ export {
29
29
  type CapabilityWords,
30
30
  capabilityWords,
31
31
  isPluginCapability,
32
+ OUTBOUND_WEBRTC,
32
33
  outboundAllows,
33
34
  PLUGIN_CAPABILITIES,
34
35
  PLUGIN_CAPABILITY_PREFIXES,
@@ -72,8 +73,10 @@ export { PluginDefinitionError } from "./plugin/definition-error.js";
72
73
  export * from "./plugin/limits.js";
73
74
  export {
74
75
  CURRENT_PLUGIN_API,
76
+ capabilitiesAsked,
75
77
  isPluginKey,
76
78
  type Localized,
79
+ legacyWidgetSize,
77
80
  localizedText,
78
81
  type ManifestParse,
79
82
  PLUGIN_API_MAJORS,
@@ -83,6 +86,8 @@ export {
83
86
  PLUGIN_PAGE_LEVELS,
84
87
  PLUGIN_SETTING_TYPES,
85
88
  PLUGIN_WIDGET_PLACES,
89
+ PLUGIN_WIDGET_SIZE_CELLS,
90
+ PLUGIN_WIDGET_SIZE_NAMES,
86
91
  PLUGIN_WIDGET_SIZES,
87
92
  type PluginBoard,
88
93
  type PluginBoardNeeds,
@@ -93,16 +98,20 @@ export {
93
98
  type PluginPageLevel,
94
99
  type PluginRole,
95
100
  type PluginSetting,
101
+ type PluginSettingGroup,
96
102
  type PluginSettingType,
97
103
  type PluginWidget,
98
104
  type PluginWidgetPlace,
99
105
  type PluginWidgetSize,
106
+ type PluginWidgetSizeName,
100
107
  parsePluginManifest,
101
108
  pluginManifestSchema,
102
109
  RESERVED_COMMAND_NAMES,
103
110
  RESERVED_PAGE_KEYS,
104
111
  RESERVED_PLUGIN_KEYS,
105
112
  SETTING_KEY_PATTERN,
113
+ TIME_OF_DAY,
114
+ widgetSizesOf,
106
115
  } from "./plugin/manifest.js";
107
116
  export {
108
117
  isPluginError,
@@ -124,3 +133,12 @@ export {
124
133
  REMOVED_GLOBALS,
125
134
  removedGlobalsIn,
126
135
  } from "./plugin/sandbox.js";
136
+ export {
137
+ compareTriples,
138
+ formatTriple,
139
+ lineRangeFrom,
140
+ parseLooseVersion,
141
+ satisfiesRange,
142
+ type Triple,
143
+ versionOfDependency,
144
+ } from "./plugin/semver.js";
package/src/rules.ts CHANGED
@@ -83,7 +83,7 @@ export function isReferenceRules(body: string): boolean {
83
83
  /** What stands in the rules for the language of the person the agent works for. */
84
84
  export const RULES_LANGUAGE_PLACEHOLDER = "{language}";
85
85
 
86
- /** What the rules say when the person chose no language: the task's, as the people of the board wrote it. */
86
+ /** What the rules say for a person with no language yet: the task's, as the people of the board wrote it. */
87
87
  const TASKS_LANGUAGE = "the language the task is written in";
88
88
 
89
89
  /** The name of a language in English, from its tag (`ko` is Korean); the tag itself when it is not one. */
@@ -101,8 +101,8 @@ export function languageNameOf(tag: string): string {
101
101
 
102
102
  /**
103
103
  * The rules as they read for a person: `{language}` filled with the name of the language they
104
- * chose for the pages, or with the task's language when they chose none. The command fills
105
- * them for the person whose token it holds; the pages for whoever reads them.
104
+ * read in (ADR-0065), or with the task's language while they have none. The command fills them
105
+ * for the person whose token it holds; the pages for whoever reads them.
106
106
  */
107
107
  export function fillRules(body: string, language: string | null | undefined): string {
108
108
  const name = language == null ? TASKS_LANGUAGE : languageNameOf(language);