@standardagents/code-plugin-sdk 1.0.0-alpha.4-hover.2 → 1.0.0-alpha.5-usage.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,8 @@ Its `package.json` includes a static `standardPlugin` manifest.
11
11
  {
12
12
  "name": "example-status",
13
13
  "type": "module",
14
- "peerDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.4-hover.2" },
15
- "devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.4-hover.2" },
14
+ "peerDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.5-usage.0" },
15
+ "devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.5-usage.0" },
16
16
  "standardPlugin": {
17
17
  "apiVersion": 1,
18
18
  "id": "example-status",
@@ -177,8 +177,15 @@ methods.
177
177
 
178
178
  `ctx.state` is per-plugin key and value storage. Standard Code stores these
179
179
  values in the user's account, so every machine the user has reads and writes
180
- the same keys. A plugin holds at most 256 keys, and one value is at most
181
- 64 KiB of JSON.
180
+ the same keys. `ctx.state.keys()` returns the sorted key names for this
181
+ plugin's account namespace. It returns an empty array when the plugin has no
182
+ keys and accepts only `RequestOptions`; it has no prefix or pagination
183
+ arguments. Another plugin cannot read these keys, even when it uses the same
184
+ account. Key names are 1 to 128 bytes. The first character is ASCII
185
+ alphanumeric or `_`; later characters are ASCII alphanumeric, `_`, `.`, `:`,
186
+ or `-`. A plugin holds at most 256 keys, and one value is at most 64 KiB of
187
+ JSON. The host applies the same account authority and revocation checks to
188
+ `keys()` as it applies to `get()` and `set()`.
182
189
  The public context declarations are in `src/index.d.ts`.
183
190
 
184
191
  ## Releases
package/REFERENCE.md CHANGED
@@ -5,7 +5,7 @@ The public declarations live in
5
5
  plugin authors.
6
6
 
7
7
  The bundled SDK and the npm package use the same public declarations. This
8
- reference describes SDK `1.0.0-alpha.4-hover.2`. Plugins should declare the
8
+ reference describes SDK `1.0.0-alpha.5-usage.0`. Plugins should declare the
9
9
  version published for their product build in both `peerDependencies` and
10
10
  `devDependencies`; the manifest `apiVersion` remains `1`.
11
11
 
@@ -44,6 +44,12 @@ panels:
44
44
 
45
45
  `PRESENTATIONS` lists the accepted presentation values.
46
46
 
47
+ Account-level sidebar cards prefer a live producer on the viewing machine.
48
+ This keeps card actions and attached panels local to that viewer. If no live
49
+ local producer exists, the host uses its deterministic remote-source fallback;
50
+ remote source authority restrictions still apply. Plugins aggregate shared
51
+ account data themselves through the public state API.
52
+
47
53
  An anchor determines the entity that owns a contribution. Machine anchors use
48
54
  a machine entity, project anchors use a project entity, and pane anchors use a
49
55
  pane entity. The `plugins` and `account` anchors are account-level surfaces;
@@ -305,17 +311,48 @@ export default definePlugin({
305
311
  `packages/plugin-sdk/test/fixtures/view-builds-monitor.json` holds a complete
306
312
  view that matches the protocol's round-trip test.
307
313
 
314
+ ### Pane header and footer slots
315
+
316
+ A `slot` on `pane.header` or `pane.footer` contributes rows inside the pane
317
+ frame, above or below its terminal body. Declarations use `width: "full"`
318
+ (the default) or `width: "half"`. Contributions stack in ascending plugin
319
+ order, then plugin ID and contribution ID. Full contributions occupy their
320
+ own rows. Consecutive half contributions share a row, with the odd column
321
+ assigned to the left. An unpaired half leaves the right half empty.
322
+ Multiline half contributions share a block as tall as the taller contribution.
323
+
324
+ Rows, styled text, and non-interactive canvas cells render in these slots.
325
+ Each slot is bounded to 32 rows. Small panes clip slot rows while preserving
326
+ terminal space. Slot height participates in the same geometry used for terminal
327
+ painting, pointer input, graphics clipping, and controller-owned resizing.
328
+ Remote viewers receive the same contributions. Missing panes, stale producer
329
+ snapshots, and mismatched pane generations withdraw their rows.
330
+
331
+ Pane-slot actions, input capture, hover popovers, and persisted slot collapse
332
+ remain unconnected. They are separate from the implemented row rendering.
333
+
334
+ `ctx.context.get()` exposes `projects`, `panes`, and `complete` for the owning
335
+ machine. Project records contain `{ entity, name, path }`; pane records contain
336
+ `{ entity, projectId, name, cwd }`. Entities include machine ID and a decimal
337
+ string generation. The pane `cwd` is its configured project launch directory.
338
+ Live shell-directory observations are a separate future capability. Each list
339
+ has at most 128 records, and their combined JSON is bounded to 32 KiB. `complete`
340
+ is false when a local path could not be decoded or a bound omitted records.
341
+ Commands, launch environment, prompts, and encrypted payloads are excluded.
342
+ Plugins must perform filesystem work on their owning machine and clear
343
+ contributions when their target disappears or changes generation.
344
+
308
345
  ### Current frontend coverage
309
346
 
310
- Sections, plugin cards, machine slots, canvas surfaces, host-rendered views,
347
+ Sections, plugin cards, machine slots, stacked pane header/footer slots, canvas surfaces, host-rendered views,
311
348
  global command palette entries, and the three
312
349
  panel presentations are implemented. A popover is content-sized within the
313
350
  modal area. A column attaches to the sidebar and resizes the workspace. A
314
351
  plugin pane has a tab and focus, drag, close, and reveal behavior; its width
315
352
  follows content and it does not create a tmux pane or resize a terminal.
316
353
 
317
- Project slot anchors, standalone badges attached to hierarchy rows, pane
318
- header/footer anchors, menu registrations, key registrations, link registrations,
354
+ Project slot anchors, standalone badges attached to hierarchy rows,
355
+ menu registrations, key registrations, link registrations,
319
356
  and entity-scoped command palette entries have protocol and composition support
320
357
  but do not have connected production frontend surfaces. Badge nodes inside a
321
358
  view tree are rendered. The `overlay`
@@ -348,6 +385,18 @@ write and focus, subscription changes, health, and webhook acknowledgements.
348
385
  Each operation has a typed input and output. `RequestOptions` accepts an abort
349
386
  signal and a timeout.
350
387
 
388
+ `ctx.state` provides account-backed `get(key)`, `set(key, value)`, and
389
+ `keys()` methods. State is isolated by the plugin ID. `keys()` takes no input
390
+ besides optional `RequestOptions` and returns a sorted `string[]` containing
391
+ the plugin's key names. The result is empty when no keys exist, is limited to
392
+ 256 keys, and has no prefix or pagination parameters. A key written by one
393
+ machine is visible to the other machines signed into the same account, so a
394
+ plugin can use distinct machine-specific key names without maintaining a
395
+ shared index. Key names are 1 to 128 bytes. The first character is ASCII
396
+ alphanumeric or `_`; later characters are ASCII alphanumeric, `_`, `.`, `:`,
397
+ or `-`. Values remain limited to 64 KiB of JSON. The host applies the same
398
+ account authority and revocation checks to all three methods.
399
+
351
400
  The public SDK applies the same capability checks and request bounds to every
352
401
  plugin. Plugin IDs do not grant fetch, pane, project, or secret exceptions.
353
402
  The current native host connects fetch, secrets, configuration, account state,
@@ -373,6 +422,9 @@ capabilities govern SDK/host operations and do not provide OS sandboxing.
373
422
  {
374
423
  "accountId": "account-id",
375
424
  "machineId": "machine-id",
425
+ "projects": [],
426
+ "panes": [],
427
+ "complete": true,
376
428
  "build": {
377
429
  "version": "0.14.1-branch.3bd7f23c.98a422",
378
430
  "commit": "<40 hex digits>",
@@ -421,6 +473,21 @@ the daemon does not deliver it when a plugin starts. Read `context.get` at
421
473
  activation for the initial values. The daemon pushes each change, so a plugin
422
474
  needs no schedule to track the followed channel.
423
475
 
476
+ Plugins with `events` can subscribe to `onEvent('pane-context', handler)`.
477
+ Its `data` replaces the local `machineId`, `projects`, `panes`, and `complete`
478
+ fields from `context.get`. Only a changed local context produces an event.
479
+ Subscribe before the initial `context.get` request and discard that response
480
+ if an event arrives while it is pending. Pane removal and generation changes
481
+ must withdraw publishers for the old entity. Project launch directories are
482
+ configured paths; shell `cd` changes are outside this event's current contract.
483
+
484
+ A plugin can watch these directories with Node's native filesystem events.
485
+ Debounce bursts with a one-shot timer, retain changes that arrive during a read,
486
+ and close watchers on disposal or target removal. Do not schedule directory
487
+ or Git refresh polling. Watch linked-worktree Git metadata and shared refs as
488
+ well as the working tree. Filesystem notification errors should report degraded
489
+ health; they must not silently switch to polling.
490
+
424
491
  ## Hooks and delivery
425
492
 
426
493
  Hook dispatch and durable webhook delivery are planned host integrations.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standardagents/code-plugin-sdk",
3
- "version": "1.0.0-alpha.4-hover.2",
3
+ "version": "1.0.0-alpha.5-usage.0",
4
4
  "type": "module",
5
5
  "description": "Standard Code plugin authoring SDK",
6
6
  "license": "MIT",
package/src/index.d.ts CHANGED
@@ -235,13 +235,27 @@ export type FleetPolicy = {
235
235
  /** The pinned or target version, or null when it is not known. */
236
236
  version: string | null;
237
237
  };
238
+ /** A local project available to plugins on its owning machine. */
239
+ export interface PluginProjectContext { entity: EntityRef; name: string; path: string }
240
+ /** Pane context with its configured project launch directory. */
241
+ export interface PluginPaneContext { entity: EntityRef; projectId: string; name: string; cwd: string }
238
242
  /** The `context.get` result. `build` and `fleet` are null when unknown. */
239
243
  export type PluginContextInfo = {
240
244
  accountId: string;
241
245
  machineId: string;
246
+ projects: PluginProjectContext[];
247
+ panes: PluginPaneContext[];
248
+ /** False when local records could not be included or decoded. */
249
+ complete: boolean;
242
250
  build?: BuildInfo | null;
243
251
  fleet?: FleetPolicy | null;
244
252
  };
253
+ /** Replaces the local pane/project context after an account change. */
254
+ export type PaneContextEventData = Pick<PluginContextInfo, 'machineId' | 'projects' | 'panes' | 'complete'>;
255
+ export interface PaneContextEvent extends PluginEvent {
256
+ name: 'pane-context';
257
+ data: PaneContextEventData;
258
+ }
245
259
  /** The `data` of a `build-context` event. */
246
260
  export type BuildContextEventData = {
247
261
  build: BuildInfo | null;
@@ -275,6 +289,7 @@ export interface OperationMap {
275
289
  'config.get': { input: Record<string, never>; output: Record<string, Json> };
276
290
  'state.get': { input: { key: string }; output: Json };
277
291
  'state.set': { input: { key: string; value: Json }; output: null };
292
+ 'state.keys': { input: Record<string, never>; output: string[] };
278
293
  'context.get': { input: Record<string, never>; output: PluginContextInfo };
279
294
  'popover.open': { input: Popover; output: { choiceId?: string; values?: Record<string, Json>; confirmationId?: string } | null };
280
295
  'canvas.write': { input: { key: ContributionKey; ansi: string }; output: null };
@@ -331,6 +346,7 @@ export interface PluginContext {
331
346
  key(id: string, handler: ActionHandler): KeyRegistration;
332
347
  link(id: string, options: LinkOptions, handler: LinkHandler): Subscription;
333
348
  link(id: string, handler: LinkHandler): Subscription;
349
+ onEvent(name: 'pane-context', handler: (event: PaneContextEvent, context: HandlerContext) => void | Promise<void>, condition?: Condition): Subscription;
334
350
  onEvent(name: 'build-context', handler: (event: BuildContextEvent, context: HandlerContext) => void | Promise<void>, condition?: Condition): Subscription;
335
351
  onEvent(name: string, handler: (event: PluginEvent, context: HandlerContext) => void | Promise<void>, condition?: Condition): Subscription;
336
352
  onHook(name: string, handler: (event: HookEvent, context: HandlerContext) => HookResult | Promise<HookResult>): Subscription;
@@ -357,8 +373,8 @@ export interface PluginContext {
357
373
  fetch(args: OperationMap['fetch']['input'], options?: RequestOptions): Promise<OperationMap['fetch']['output']>;
358
374
  secrets: { get(name: string, options?: RequestOptions): Promise<string | null> };
359
375
  config: { get(options?: RequestOptions): Promise<Record<string, Json>> };
360
- /** Per-plugin key and value storage, held in the user's account and shared across their machines: 256 keys per plugin, 64 KiB per value. */
361
- state: { get(key: string, options?: RequestOptions): Promise<Json>; set(key: string, value: Json, options?: RequestOptions): Promise<null> };
376
+ /** Per-plugin key and value storage, held in the user's account and shared across their machines: 256 keys per plugin, 64 KiB per value. `keys()` returns the sorted key names. */
377
+ state: { get(key: string, options?: RequestOptions): Promise<Json>; set(key: string, value: Json, options?: RequestOptions): Promise<null>; keys(options?: RequestOptions): Promise<string[]> };
362
378
  context: { get(options?: RequestOptions): Promise<PluginContextInfo> };
363
379
  popover: { open(args: Popover, options?: RequestOptions): Promise<OperationMap['popover.open']['output']> };
364
380
  health: { set(args: OperationMap['health.set']['input'], options?: RequestOptions): Promise<null> };
package/src/protocol.mjs CHANGED
@@ -8,7 +8,7 @@ export const OPERATIONS = Object.freeze({
8
8
  'pane.input': 'panes', 'pane.focus': 'panes', 'pane.wait': 'panes',
9
9
  'project.create': 'projects', 'project.remove': 'projects',
10
10
  'notification.show': 'notifications', 'url.open': 'url', fetch: 'fetch',
11
- 'secret.get': 'secrets', 'config.get': null, 'state.get': null, 'state.set': null,
11
+ 'secret.get': 'secrets', 'config.get': null, 'state.get': null, 'state.set': null, 'state.keys': null,
12
12
  'context.get': null, 'popover.open': 'surfaces', 'canvas.write': 'surfaces',
13
13
  'canvas.focus': 'surfaces', 'subscription.add': null, 'subscription.remove': null,
14
14
  'health.set': null, 'webhook.ack': 'webhook',
package/src/runtime.mjs CHANGED
@@ -372,7 +372,8 @@ export function createRuntime({ manifest: input, producer, send, clock = realClo
372
372
  secrets: Object.freeze({ get: (name, options) => request('secret.get', { name }, options) }),
373
373
  config: Object.freeze({ get: options => request('config.get', {}, options) }),
374
374
  state: Object.freeze({ get: (key, options) => request('state.get', { key }, options),
375
- set: (key, value, options) => request('state.set', { key, value }, options) }),
375
+ set: (key, value, options) => request('state.set', { key, value }, options),
376
+ keys: options => request('state.keys', {}, options) }),
376
377
  context: Object.freeze({ get: options => request('context.get', {}, options) }),
377
378
  popover: Object.freeze({ open: method('popover.open') }),
378
379
  health: Object.freeze({ set: method('health.set') }),
package/src/testing.mjs CHANGED
@@ -43,8 +43,9 @@ export function createHarness({ manifest: input, machineId = 'test-machine', epo
43
43
  ensure(accountState.has(args.key) || accountState.size < LIMITS.stateKeys, 'queue_full', 'Harness state holds 256 keys for this plugin')
44
44
  jsonBytes(args.value, LIMITS.stateValueBytes)
45
45
  accountState.set(args.key, structuredClone(args.value)); return null
46
+ case 'state.keys': return [...accountState.keys()].sort()
46
47
  case 'config.get': return {}
47
- case 'context.get': return { accountId: 'test-account', machineId, build: null, fleet: null }
48
+ case 'context.get': return { accountId: 'test-account', machineId, build: null, fleet: null, projects: [], panes: [], complete: true }
48
49
  case 'health.set': return null
49
50
  default: throw new PluginError('missing_fixture', `Provide a harness handler for ${operation.op}`)
50
51
  }