@pstdio/sdk 0.24.1 → 0.25.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/dist/api/index.js CHANGED
@@ -5229,7 +5229,19 @@ var listExtensionConnectionsResponseSchema = object({
5229
5229
  connections: array(extensionConnectionRecordSchema)
5230
5230
  });
5231
5231
 
5232
- // ../pstdio-api-contracts/src/extensions/execute.ts
5232
+ // ../pstdio-api-contracts/src/extension-kernel/navigation-href.ts
5233
+ var isNavigationHref = (href) => {
5234
+ if (typeof href !== "string")
5235
+ return false;
5236
+ try {
5237
+ const { protocol } = new URL(href);
5238
+ return protocol === "https:" || protocol === "http:";
5239
+ } catch {
5240
+ return false;
5241
+ }
5242
+ };
5243
+
5244
+ // ../pstdio-api-contracts/src/extensions/resource-ref.ts
5233
5245
  var extensionResourceRefSchema = object({
5234
5246
  shorthand: string2().optional(),
5235
5247
  type: string2(),
@@ -5240,6 +5252,54 @@ var extensionResourceRefSchema = object({
5240
5252
  extensionId: string2().optional(),
5241
5253
  metadata: serializableJsonObjectSchema.optional()
5242
5254
  });
5255
+
5256
+ // ../pstdio-api-contracts/src/extensions/workbench-refs-metadata.ts
5257
+ var contributionRefSchema = (kind) => object({ extensionId: string2(), kind: literal(kind), id: string2() });
5258
+ var pageRefSchema = contributionRefSchema("page");
5259
+ var placementRefSchema = contributionRefSchema("placement");
5260
+ var modeRefSchema = contributionRefSchema("mode");
5261
+ var viewRefSchema = contributionRefSchema("view");
5262
+ var resourceKindRefSchema = contributionRefSchema("resource-kind");
5263
+ var pageSlotRefSchema = object({
5264
+ kind: literal("page-slot"),
5265
+ page: pageRefSchema,
5266
+ id: string2()
5267
+ });
5268
+ var panelRefSchema = union([placementRefSchema, pageSlotRefSchema]);
5269
+ var commandTargetSchema = object({
5270
+ command: contributionRefSchema("command"),
5271
+ params: serializableJsonObjectSchema.optional()
5272
+ });
5273
+
5274
+ // ../pstdio-api-contracts/src/extensions/navigation-target-metadata.ts
5275
+ var sectionSchema = object({
5276
+ anchors: array(object({ id: string2(), heading: string2(), occurrence: number2().int().nonnegative().optional() }))
5277
+ });
5278
+ var pageTargetSchema = lazy(() => object({
5279
+ kind: literal("page"),
5280
+ page: pageRefSchema,
5281
+ resource: extensionResourceRefSchema.optional(),
5282
+ open: _enum(["preview", "pin"]).optional(),
5283
+ section: sectionSchema.optional(),
5284
+ parent: pageTargetSchema.optional()
5285
+ })).meta({ id: "WorkbenchMetadataPageTarget" });
5286
+ var navigationOperationSchema = union([
5287
+ pageTargetSchema,
5288
+ object({
5289
+ kind: literal("panel"),
5290
+ panel: panelRefSchema,
5291
+ resource: extensionResourceRefSchema.optional(),
5292
+ open: _enum(["preview", "pin"]).optional()
5293
+ })
5294
+ ]);
5295
+ var navigationTargetSchema = union([
5296
+ navigationOperationSchema,
5297
+ object({ kind: literal("command"), target: commandTargetSchema }),
5298
+ object({ kind: literal("href"), href: string2().refine(isNavigationHref, "Expected an HTTP or HTTPS URL") }),
5299
+ object({ kind: literal("compound"), targets: array(navigationOperationSchema).min(1) })
5300
+ ]);
5301
+
5302
+ // ../pstdio-api-contracts/src/extensions/execute.ts
5243
5303
  var extensionRepoContextSchema = object({
5244
5304
  projectId: string2(),
5245
5305
  repoId: string2(),
@@ -5309,10 +5369,12 @@ var commandDiagnosticSchema = object({
5309
5369
  commandId: string2().optional(),
5310
5370
  metadata: jsonObjectSchema.optional()
5311
5371
  });
5372
+ var responseNavigationTargetSchema = navigationTargetSchema;
5312
5373
  var commandOutcomeSchema = object({
5313
5374
  ok: boolean2(),
5314
5375
  status: _enum(["success", "rejected", "error"]),
5315
5376
  value: unknown().optional(),
5377
+ navigationRequests: array(responseNavigationTargetSchema).optional(),
5316
5378
  code: string2().optional(),
5317
5379
  reason: string2().optional(),
5318
5380
  data: jsonObjectSchema.optional(),
@@ -5682,52 +5744,6 @@ var extensionCommandPaletteResourceRecordSchema = object({
5682
5744
  refreshEventIds: array(string2().min(1)).optional()
5683
5745
  });
5684
5746
 
5685
- // ../pstdio-api-contracts/src/extensions/workbench-refs-metadata.ts
5686
- var contributionRefSchema = (kind) => object({ extensionId: string2(), kind: literal(kind), id: string2() });
5687
- var pageRefSchema = contributionRefSchema("page");
5688
- var placementRefSchema = contributionRefSchema("placement");
5689
- var modeRefSchema = contributionRefSchema("mode");
5690
- var viewRefSchema = contributionRefSchema("view");
5691
- var resourceKindRefSchema = contributionRefSchema("resource-kind");
5692
- var pageSlotRefSchema = object({
5693
- kind: literal("page-slot"),
5694
- page: pageRefSchema,
5695
- id: string2()
5696
- });
5697
- var panelRefSchema = union([placementRefSchema, pageSlotRefSchema]);
5698
- var commandTargetSchema = object({
5699
- command: contributionRefSchema("command"),
5700
- params: serializableJsonObjectSchema.optional()
5701
- });
5702
-
5703
- // ../pstdio-api-contracts/src/extensions/navigation-target-metadata.ts
5704
- var sectionSchema = object({
5705
- anchors: array(object({ id: string2(), heading: string2(), occurrence: number2().int().nonnegative().optional() }))
5706
- });
5707
- var pageTargetSchema = lazy(() => object({
5708
- kind: literal("page"),
5709
- page: pageRefSchema,
5710
- resource: extensionResourceRefSchema.optional(),
5711
- open: _enum(["preview", "pin"]).optional(),
5712
- section: sectionSchema.optional(),
5713
- parent: pageTargetSchema.optional()
5714
- })).meta({ id: "WorkbenchMetadataPageTarget" });
5715
- var navigationOperationSchema = union([
5716
- pageTargetSchema,
5717
- object({
5718
- kind: literal("panel"),
5719
- panel: panelRefSchema,
5720
- resource: extensionResourceRefSchema.optional(),
5721
- open: _enum(["preview", "pin"]).optional()
5722
- })
5723
- ]);
5724
- var navigationTargetSchema = union([
5725
- navigationOperationSchema,
5726
- object({ kind: literal("command"), target: commandTargetSchema }),
5727
- object({ kind: literal("href"), href: string2() }),
5728
- object({ kind: literal("compound"), targets: array(navigationOperationSchema).min(1) })
5729
- ]);
5730
-
5731
5747
  // ../pstdio-api-contracts/src/extensions/keybindings.ts
5732
5748
  var parsedKeybindingChordSchema = object({
5733
5749
  key: string2(),
@@ -6453,6 +6469,86 @@ var l10n = (key, defaultValue) => ({
6453
6469
  ...defaultValue === undefined ? {} : { default: defaultValue }
6454
6470
  });
6455
6471
  var isLocalizedString = (value) => typeof value === "object" && value !== null && ("$l10n" in value) && typeof value.$l10n === "string";
6472
+ // ../pstdio-api-contracts/src/extension-kernel/navigation.ts
6473
+ var isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
6474
+ var isContributionRef = (value, kind) => isRecord(value) && value.kind === kind && typeof value.id === "string";
6475
+ var isResource = (value) => isRecord(value) && typeof value.type === "string" && typeof value.id === "string";
6476
+ var isOpenIntent = (value) => value === undefined || value === "preview" || value === "pin";
6477
+ var isPageTarget = (value, ancestors = new Set) => {
6478
+ if (!isRecord(value) || value.kind !== "page" || !isContributionRef(value.page, "page"))
6479
+ return false;
6480
+ if (ancestors.has(value))
6481
+ return false;
6482
+ if (value.resource !== undefined && !isResource(value.resource))
6483
+ return false;
6484
+ if (value.section !== undefined && !isRecord(value.section))
6485
+ return false;
6486
+ if (!isOpenIntent(value.open))
6487
+ return false;
6488
+ if (value.parent === undefined)
6489
+ return true;
6490
+ const next = new Set(ancestors);
6491
+ next.add(value);
6492
+ return isPageTarget(value.parent, next);
6493
+ };
6494
+ var isPanelTarget = (value) => {
6495
+ if (!isRecord(value) || value.kind !== "panel" || !isRecord(value.panel))
6496
+ return false;
6497
+ const panel = value.panel;
6498
+ const validPanel = isContributionRef(panel, "placement") || panel.kind === "page-slot" && isContributionRef(panel.page, "page") && typeof panel.id === "string";
6499
+ return validPanel && (value.resource === undefined || isResource(value.resource)) && isOpenIntent(value.open);
6500
+ };
6501
+ var isItemTarget = (value) => {
6502
+ if (!isRecord(value))
6503
+ return false;
6504
+ if (value.kind === "page")
6505
+ return isPageTarget(value);
6506
+ if (value.kind === "panel")
6507
+ return isPanelTarget(value);
6508
+ if (value.kind === "href")
6509
+ return isNavigationHref(value.href);
6510
+ return value.kind === "command" && isRecord(value.target) && isContributionRef(value.target.command, "command") && (value.target.params === undefined || isRecord(value.target.params));
6511
+ };
6512
+ var isNavigationTarget = (value) => {
6513
+ if (isItemTarget(value))
6514
+ return true;
6515
+ if (!isRecord(value) || value.kind !== "compound" || !Array.isArray(value.targets) || value.targets.length === 0) {
6516
+ return false;
6517
+ }
6518
+ return value.targets.every((target) => isPageTarget(target) || isPanelTarget(target));
6519
+ };
6520
+ var withExtensionOwner = (ref, extensionId) => ref.extensionId !== undefined || extensionId === undefined ? ref : { ...ref, extensionId };
6521
+ var toPageTarget = (target, extensionId, projectId) => ({
6522
+ ...target,
6523
+ page: withExtensionOwner(target.page, extensionId),
6524
+ ...target.resource ? { resource: qualifyResource(target.resource, extensionId, projectId) } : {},
6525
+ ...target.parent ? { parent: toPageTarget(target.parent, extensionId, projectId) } : {}
6526
+ });
6527
+ var qualifyResource = (resource, extensionId, projectId) => ({
6528
+ ...resource,
6529
+ extensionId: resource.extensionId ?? extensionId,
6530
+ projectId: resource.projectId ?? projectId
6531
+ });
6532
+ var qualifyNavigationTarget = (target, extensionId, projectId) => {
6533
+ if (!isNavigationTarget(target))
6534
+ throw new Error("Invalid navigation target.");
6535
+ if (target.kind === "page")
6536
+ return toPageTarget(target, extensionId, projectId);
6537
+ if (target.kind === "panel")
6538
+ return {
6539
+ ...target,
6540
+ panel: target.panel.kind === "page-slot" ? { ...target.panel, page: withExtensionOwner(target.panel.page, extensionId) } : withExtensionOwner(target.panel, extensionId),
6541
+ ...target.resource ? { resource: qualifyResource(target.resource, extensionId, projectId) } : {}
6542
+ };
6543
+ if (target.kind === "command")
6544
+ return { ...target, target: { ...target.target, command: withExtensionOwner(target.target.command, extensionId) } };
6545
+ if (target.kind === "href")
6546
+ return target;
6547
+ return {
6548
+ ...target,
6549
+ targets: target.targets.map((item) => qualifyNavigationTarget(item, extensionId, projectId))
6550
+ };
6551
+ };
6456
6552
  // ../pstdio-api-contracts/src/extension-kernel/package-asset.ts
6457
6553
  var packageAsset = (path, baseUrl) => ({
6458
6554
  kind: "package-asset",
package/dist/api.d.ts CHANGED
@@ -180,6 +180,7 @@ declare const commandExecuteResponseSchema: z.ZodObject<{
180
180
  rejected: "rejected";
181
181
  }>;
182
182
  value: z.ZodOptional<z.ZodUnknown>;
183
+ navigationRequests: z.ZodOptional<z.ZodArray<z.ZodType<NavigationTarget, unknown, z.core.$ZodTypeInternals<NavigationTarget, unknown>>>>;
183
184
  code: z.ZodOptional<z.ZodString>;
184
185
  reason: z.ZodOptional<z.ZodString>;
185
186
  data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
package/dist/client.d.ts CHANGED
@@ -244,6 +244,7 @@ declare const commandExecuteResponseSchema: z.ZodObject<{
244
244
  rejected: "rejected";
245
245
  }>;
246
246
  value: z.ZodOptional<z.ZodUnknown>;
247
+ navigationRequests: z.ZodOptional<z.ZodArray<z.ZodType<NavigationTarget, unknown, z.core.$ZodTypeInternals<NavigationTarget, unknown>>>>;
247
248
  code: z.ZodOptional<z.ZodString>;
248
249
  reason: z.ZodOptional<z.ZodString>;
249
250
  data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
package/dist/data.d.ts CHANGED
@@ -10,6 +10,8 @@ declare interface ArtifactMount {
10
10
  exists(path: string): Promise<boolean>;
11
11
  readText(path: string): Promise<string>;
12
12
  writeText(path: string, value: string): Promise<void>;
13
+ /** Replace existing text. Fails if missing and never recreates a concurrently deleted file. */
14
+ updateText(path: string, value: string): Promise<void>;
13
15
  readBytes(path: string): Promise<Uint8Array>;
14
16
  writeBytes(path: string, value: Uint8Array): Promise<void>;
15
17
  list(pattern?: string): Promise<ArtifactFile[]>;
@@ -163,6 +163,98 @@ var l10n = (key, defaultValue) => ({
163
163
  ...defaultValue === undefined ? {} : { default: defaultValue }
164
164
  });
165
165
  var isLocalizedString = (value) => typeof value === "object" && value !== null && ("$l10n" in value) && typeof value.$l10n === "string";
166
+ // ../pstdio-api-contracts/src/extension-kernel/navigation-href.ts
167
+ var isNavigationHref = (href) => {
168
+ if (typeof href !== "string")
169
+ return false;
170
+ try {
171
+ const { protocol } = new URL(href);
172
+ return protocol === "https:" || protocol === "http:";
173
+ } catch {
174
+ return false;
175
+ }
176
+ };
177
+
178
+ // ../pstdio-api-contracts/src/extension-kernel/navigation.ts
179
+ var isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
180
+ var isContributionRef = (value, kind) => isRecord(value) && value.kind === kind && typeof value.id === "string";
181
+ var isResource = (value) => isRecord(value) && typeof value.type === "string" && typeof value.id === "string";
182
+ var isOpenIntent = (value) => value === undefined || value === "preview" || value === "pin";
183
+ var isPageTarget = (value, ancestors = new Set) => {
184
+ if (!isRecord(value) || value.kind !== "page" || !isContributionRef(value.page, "page"))
185
+ return false;
186
+ if (ancestors.has(value))
187
+ return false;
188
+ if (value.resource !== undefined && !isResource(value.resource))
189
+ return false;
190
+ if (value.section !== undefined && !isRecord(value.section))
191
+ return false;
192
+ if (!isOpenIntent(value.open))
193
+ return false;
194
+ if (value.parent === undefined)
195
+ return true;
196
+ const next = new Set(ancestors);
197
+ next.add(value);
198
+ return isPageTarget(value.parent, next);
199
+ };
200
+ var isPanelTarget = (value) => {
201
+ if (!isRecord(value) || value.kind !== "panel" || !isRecord(value.panel))
202
+ return false;
203
+ const panel = value.panel;
204
+ const validPanel = isContributionRef(panel, "placement") || panel.kind === "page-slot" && isContributionRef(panel.page, "page") && typeof panel.id === "string";
205
+ return validPanel && (value.resource === undefined || isResource(value.resource)) && isOpenIntent(value.open);
206
+ };
207
+ var isItemTarget = (value) => {
208
+ if (!isRecord(value))
209
+ return false;
210
+ if (value.kind === "page")
211
+ return isPageTarget(value);
212
+ if (value.kind === "panel")
213
+ return isPanelTarget(value);
214
+ if (value.kind === "href")
215
+ return isNavigationHref(value.href);
216
+ return value.kind === "command" && isRecord(value.target) && isContributionRef(value.target.command, "command") && (value.target.params === undefined || isRecord(value.target.params));
217
+ };
218
+ var isNavigationTarget = (value) => {
219
+ if (isItemTarget(value))
220
+ return true;
221
+ if (!isRecord(value) || value.kind !== "compound" || !Array.isArray(value.targets) || value.targets.length === 0) {
222
+ return false;
223
+ }
224
+ return value.targets.every((target) => isPageTarget(target) || isPanelTarget(target));
225
+ };
226
+ var withExtensionOwner = (ref, extensionId) => ref.extensionId !== undefined || extensionId === undefined ? ref : { ...ref, extensionId };
227
+ var toPageTarget = (target, extensionId, projectId) => ({
228
+ ...target,
229
+ page: withExtensionOwner(target.page, extensionId),
230
+ ...target.resource ? { resource: qualifyResource(target.resource, extensionId, projectId) } : {},
231
+ ...target.parent ? { parent: toPageTarget(target.parent, extensionId, projectId) } : {}
232
+ });
233
+ var qualifyResource = (resource, extensionId, projectId) => ({
234
+ ...resource,
235
+ extensionId: resource.extensionId ?? extensionId,
236
+ projectId: resource.projectId ?? projectId
237
+ });
238
+ var qualifyNavigationTarget = (target, extensionId, projectId) => {
239
+ if (!isNavigationTarget(target))
240
+ throw new Error("Invalid navigation target.");
241
+ if (target.kind === "page")
242
+ return toPageTarget(target, extensionId, projectId);
243
+ if (target.kind === "panel")
244
+ return {
245
+ ...target,
246
+ panel: target.panel.kind === "page-slot" ? { ...target.panel, page: withExtensionOwner(target.panel.page, extensionId) } : withExtensionOwner(target.panel, extensionId),
247
+ ...target.resource ? { resource: qualifyResource(target.resource, extensionId, projectId) } : {}
248
+ };
249
+ if (target.kind === "command")
250
+ return { ...target, target: { ...target.target, command: withExtensionOwner(target.target.command, extensionId) } };
251
+ if (target.kind === "href")
252
+ return target;
253
+ return {
254
+ ...target,
255
+ targets: target.targets.map((item) => qualifyNavigationTarget(item, extensionId, projectId))
256
+ };
257
+ };
166
258
  // ../pstdio-api-contracts/src/extension-kernel/package-asset.ts
167
259
  var packageAsset = (path, baseUrl) => ({
168
260
  kind: "package-asset",
@@ -200,7 +292,7 @@ var WEBVIEW_HOST_CAPABILITIES = [
200
292
  ...ALWAYS_AVAILABLE_WEBVIEW_CAPABILITIES
201
293
  ];
202
294
  // package.json
203
- var version = "0.24.1";
295
+ var version = "0.25.0";
204
296
 
205
297
  // src/version.ts
206
298
  var SDK_VERSION = version;
@@ -733,6 +825,7 @@ export {
733
825
  extensionPanelRegions,
734
826
  gitEvents,
735
827
  isLocalizedString,
828
+ isNavigationTarget,
736
829
  isValidLocalContributionId,
737
830
  isWorkbenchProjectUrl,
738
831
  l10n,
@@ -746,6 +839,7 @@ export {
746
839
  projectEvents,
747
840
  projectPrefix,
748
841
  projectSlots,
842
+ qualifyNavigationTarget,
749
843
  qualifyRef,
750
844
  resolveEventReferenceId,
751
845
  resourceKey,
@@ -99,6 +99,8 @@ export declare interface ArtifactMount {
99
99
  exists(path: string): Promise<boolean>;
100
100
  readText(path: string): Promise<string>;
101
101
  writeText(path: string, value: string): Promise<void>;
102
+ /** Replace existing text. Fails if missing and never recreates a concurrently deleted file. */
103
+ updateText(path: string, value: string): Promise<void>;
102
104
  readBytes(path: string): Promise<Uint8Array>;
103
105
  writeBytes(path: string, value: Uint8Array): Promise<void>;
104
106
  list(pattern?: string): Promise<ArtifactFile[]>;
@@ -303,6 +305,7 @@ export declare type CommandOutcome<TResult = unknown> = {
303
305
  ok: true;
304
306
  status: "success";
305
307
  value: TResult;
308
+ navigationRequests?: NavigationTarget[];
306
309
  notices?: CommandNotice[];
307
310
  diagnostics?: CommandDiagnostic[];
308
311
  } | {
@@ -652,7 +655,7 @@ export declare interface DataTableRendererRowAction<TParams extends Struct = Str
652
655
 
653
656
  export declare type DataTableRendererRowActivationHandler = RendererCallback<{
654
657
  row: DataTableRendererRow;
655
- }, undefined | NavigationTarget>;
658
+ }, void | NavigationTarget>;
656
659
 
657
660
  export declare interface DataTableRendererSelectionAction<TParams extends Struct = Struct> {
658
661
  id: string;
@@ -933,6 +936,10 @@ export declare interface ExtensionContextBase<TSettings extends Record<string, u
933
936
  source?: CommandSource;
934
937
  storage: ExtensionStorageApi;
935
938
  resources: ExtensionResourcesApi;
939
+ /** Records navigation for the invoking UI; headless execution opens nothing. */
940
+ navigation: {
941
+ open(target: NavigationTarget): void;
942
+ };
936
943
  artifacts: ExtensionArtifactApi;
937
944
  /** Working tree of the invocation's repo, scoped to its root. Absent for non-repo (event/hook) invocations. */
938
945
  repoFiles?: ArtifactMount;
@@ -1064,6 +1071,8 @@ export declare interface ExtensionReposApi {
1064
1071
  }
1065
1072
 
1066
1073
  export declare interface ExtensionResourcesApi {
1074
+ /** Publish a committed removal to clients displaying this project resource. */
1075
+ removed(resource: ResourceRef): Promise<void>;
1067
1076
  allocate(input: {
1068
1077
  kind: string;
1069
1078
  }): Promise<{
@@ -1173,6 +1182,8 @@ export declare interface ExtensionStorageCollectionApi<TItem = unknown> {
1173
1182
  get(id: string): Promise<TItem | undefined>;
1174
1183
  list(): Promise<TItem[]>;
1175
1184
  put(id: string, value: TItem): Promise<void>;
1185
+ /** Replace an existing item atomically. Throws if the item was deleted. */
1186
+ update(id: string, value: TItem): Promise<void>;
1176
1187
  createIfAbsent(id: string, value: TItem): Promise<boolean>;
1177
1188
  deleteIfValue(id: string, value: TItem): Promise<boolean>;
1178
1189
  create(value: TItem): Promise<TItem & {
@@ -1488,6 +1499,8 @@ export declare interface HookDefinition<TPayload extends Struct = Struct> extend
1488
1499
 
1489
1500
  export declare const isLocalizedString: (value: unknown) => value is LocalizedString;
1490
1501
 
1502
+ export declare const isNavigationTarget: (value: unknown) => value is NavigationTarget;
1503
+
1491
1504
  export declare const isValidLocalContributionId: (id: string) => boolean;
1492
1505
 
1493
1506
  export declare const isWorkbenchProjectUrl: (urlValue: string, projectId: string) => boolean;
@@ -1646,7 +1659,7 @@ export declare interface KanbanRendererRowAction<TParams extends Struct = Struct
1646
1659
 
1647
1660
  export declare type KanbanRendererRowActivationHandler = RendererCallback<{
1648
1661
  row: KanbanRendererRow;
1649
- }, undefined | NavigationTarget>;
1662
+ }, void | NavigationTarget>;
1650
1663
 
1651
1664
  export declare interface KanbanRendererSavedView {
1652
1665
  id: string;
@@ -2332,6 +2345,8 @@ export declare type QualifiedRef<Ref extends QualifiableRef> = Ref extends PageS
2332
2345
  readonly extensionId: string;
2333
2346
  };
2334
2347
 
2348
+ export declare const qualifyNavigationTarget: (target: NavigationTarget, extensionId: string, projectId?: string) => NavigationTarget;
2349
+
2335
2350
  /** Publish a provider-owned reference without changing the local contribution definition. */
2336
2351
  export declare function qualifyRef<Ref extends QualifiableRef>(owner: string, ref: Ref): QualifiedRef<Ref>;
2337
2352
 
@@ -2552,6 +2567,11 @@ export declare interface ResourceRef {
2552
2567
  /** A reference the host can open (e.g. another resource tab). */
2553
2568
  export declare type ResourceRefValue = ResourceRef;
2554
2569
 
2570
+ export declare interface ResourceRemovedEvent {
2571
+ id: string;
2572
+ resource: ResourceRef;
2573
+ }
2574
+
2555
2575
  export declare type ResourceRole = "primary" | "context" | "source" | "result";
2556
2576
 
2557
2577
  /** A reattach error is permanent unless the provider explicitly marks it retryable. */
package/dist/hooks.d.ts CHANGED
@@ -242,6 +242,7 @@ declare const commandExecuteResponseSchema: z.ZodObject<{
242
242
  rejected: "rejected";
243
243
  }>;
244
244
  value: z.ZodOptional<z.ZodUnknown>;
245
+ navigationRequests: z.ZodOptional<z.ZodArray<z.ZodType<NavigationTarget, unknown, z.core.$ZodTypeInternals<NavigationTarget, unknown>>>>;
245
246
  code: z.ZodOptional<z.ZodString>;
246
247
  reason: z.ZodOptional<z.ZodString>;
247
248
  data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -5229,7 +5229,19 @@ var listExtensionConnectionsResponseSchema = object({
5229
5229
  connections: array(extensionConnectionRecordSchema)
5230
5230
  });
5231
5231
 
5232
- // ../pstdio-api-contracts/src/extensions/execute.ts
5232
+ // ../pstdio-api-contracts/src/extension-kernel/navigation-href.ts
5233
+ var isNavigationHref = (href) => {
5234
+ if (typeof href !== "string")
5235
+ return false;
5236
+ try {
5237
+ const { protocol } = new URL(href);
5238
+ return protocol === "https:" || protocol === "http:";
5239
+ } catch {
5240
+ return false;
5241
+ }
5242
+ };
5243
+
5244
+ // ../pstdio-api-contracts/src/extensions/resource-ref.ts
5233
5245
  var extensionResourceRefSchema = object({
5234
5246
  shorthand: string2().optional(),
5235
5247
  type: string2(),
@@ -5240,6 +5252,54 @@ var extensionResourceRefSchema = object({
5240
5252
  extensionId: string2().optional(),
5241
5253
  metadata: serializableJsonObjectSchema.optional()
5242
5254
  });
5255
+
5256
+ // ../pstdio-api-contracts/src/extensions/workbench-refs-metadata.ts
5257
+ var contributionRefSchema = (kind) => object({ extensionId: string2(), kind: literal(kind), id: string2() });
5258
+ var pageRefSchema = contributionRefSchema("page");
5259
+ var placementRefSchema = contributionRefSchema("placement");
5260
+ var modeRefSchema = contributionRefSchema("mode");
5261
+ var viewRefSchema = contributionRefSchema("view");
5262
+ var resourceKindRefSchema = contributionRefSchema("resource-kind");
5263
+ var pageSlotRefSchema = object({
5264
+ kind: literal("page-slot"),
5265
+ page: pageRefSchema,
5266
+ id: string2()
5267
+ });
5268
+ var panelRefSchema = union([placementRefSchema, pageSlotRefSchema]);
5269
+ var commandTargetSchema = object({
5270
+ command: contributionRefSchema("command"),
5271
+ params: serializableJsonObjectSchema.optional()
5272
+ });
5273
+
5274
+ // ../pstdio-api-contracts/src/extensions/navigation-target-metadata.ts
5275
+ var sectionSchema = object({
5276
+ anchors: array(object({ id: string2(), heading: string2(), occurrence: number2().int().nonnegative().optional() }))
5277
+ });
5278
+ var pageTargetSchema = lazy(() => object({
5279
+ kind: literal("page"),
5280
+ page: pageRefSchema,
5281
+ resource: extensionResourceRefSchema.optional(),
5282
+ open: _enum(["preview", "pin"]).optional(),
5283
+ section: sectionSchema.optional(),
5284
+ parent: pageTargetSchema.optional()
5285
+ })).meta({ id: "WorkbenchMetadataPageTarget" });
5286
+ var navigationOperationSchema = union([
5287
+ pageTargetSchema,
5288
+ object({
5289
+ kind: literal("panel"),
5290
+ panel: panelRefSchema,
5291
+ resource: extensionResourceRefSchema.optional(),
5292
+ open: _enum(["preview", "pin"]).optional()
5293
+ })
5294
+ ]);
5295
+ var navigationTargetSchema = union([
5296
+ navigationOperationSchema,
5297
+ object({ kind: literal("command"), target: commandTargetSchema }),
5298
+ object({ kind: literal("href"), href: string2().refine(isNavigationHref, "Expected an HTTP or HTTPS URL") }),
5299
+ object({ kind: literal("compound"), targets: array(navigationOperationSchema).min(1) })
5300
+ ]);
5301
+
5302
+ // ../pstdio-api-contracts/src/extensions/execute.ts
5243
5303
  var extensionRepoContextSchema = object({
5244
5304
  projectId: string2(),
5245
5305
  repoId: string2(),
@@ -5309,10 +5369,12 @@ var commandDiagnosticSchema = object({
5309
5369
  commandId: string2().optional(),
5310
5370
  metadata: jsonObjectSchema.optional()
5311
5371
  });
5372
+ var responseNavigationTargetSchema = navigationTargetSchema;
5312
5373
  var commandOutcomeSchema = object({
5313
5374
  ok: boolean2(),
5314
5375
  status: _enum(["success", "rejected", "error"]),
5315
5376
  value: unknown().optional(),
5377
+ navigationRequests: array(responseNavigationTargetSchema).optional(),
5316
5378
  code: string2().optional(),
5317
5379
  reason: string2().optional(),
5318
5380
  data: jsonObjectSchema.optional(),
@@ -5682,52 +5744,6 @@ var extensionCommandPaletteResourceRecordSchema = object({
5682
5744
  refreshEventIds: array(string2().min(1)).optional()
5683
5745
  });
5684
5746
 
5685
- // ../pstdio-api-contracts/src/extensions/workbench-refs-metadata.ts
5686
- var contributionRefSchema = (kind) => object({ extensionId: string2(), kind: literal(kind), id: string2() });
5687
- var pageRefSchema = contributionRefSchema("page");
5688
- var placementRefSchema = contributionRefSchema("placement");
5689
- var modeRefSchema = contributionRefSchema("mode");
5690
- var viewRefSchema = contributionRefSchema("view");
5691
- var resourceKindRefSchema = contributionRefSchema("resource-kind");
5692
- var pageSlotRefSchema = object({
5693
- kind: literal("page-slot"),
5694
- page: pageRefSchema,
5695
- id: string2()
5696
- });
5697
- var panelRefSchema = union([placementRefSchema, pageSlotRefSchema]);
5698
- var commandTargetSchema = object({
5699
- command: contributionRefSchema("command"),
5700
- params: serializableJsonObjectSchema.optional()
5701
- });
5702
-
5703
- // ../pstdio-api-contracts/src/extensions/navigation-target-metadata.ts
5704
- var sectionSchema = object({
5705
- anchors: array(object({ id: string2(), heading: string2(), occurrence: number2().int().nonnegative().optional() }))
5706
- });
5707
- var pageTargetSchema = lazy(() => object({
5708
- kind: literal("page"),
5709
- page: pageRefSchema,
5710
- resource: extensionResourceRefSchema.optional(),
5711
- open: _enum(["preview", "pin"]).optional(),
5712
- section: sectionSchema.optional(),
5713
- parent: pageTargetSchema.optional()
5714
- })).meta({ id: "WorkbenchMetadataPageTarget" });
5715
- var navigationOperationSchema = union([
5716
- pageTargetSchema,
5717
- object({
5718
- kind: literal("panel"),
5719
- panel: panelRefSchema,
5720
- resource: extensionResourceRefSchema.optional(),
5721
- open: _enum(["preview", "pin"]).optional()
5722
- })
5723
- ]);
5724
- var navigationTargetSchema = union([
5725
- navigationOperationSchema,
5726
- object({ kind: literal("command"), target: commandTargetSchema }),
5727
- object({ kind: literal("href"), href: string2() }),
5728
- object({ kind: literal("compound"), targets: array(navigationOperationSchema).min(1) })
5729
- ]);
5730
-
5731
5747
  // ../pstdio-api-contracts/src/extensions/keybindings.ts
5732
5748
  var parsedKeybindingChordSchema = object({
5733
5749
  key: string2(),
@@ -6453,6 +6469,86 @@ var l10n = (key, defaultValue) => ({
6453
6469
  ...defaultValue === undefined ? {} : { default: defaultValue }
6454
6470
  });
6455
6471
  var isLocalizedString = (value) => typeof value === "object" && value !== null && ("$l10n" in value) && typeof value.$l10n === "string";
6472
+ // ../pstdio-api-contracts/src/extension-kernel/navigation.ts
6473
+ var isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
6474
+ var isContributionRef = (value, kind) => isRecord(value) && value.kind === kind && typeof value.id === "string";
6475
+ var isResource = (value) => isRecord(value) && typeof value.type === "string" && typeof value.id === "string";
6476
+ var isOpenIntent = (value) => value === undefined || value === "preview" || value === "pin";
6477
+ var isPageTarget = (value, ancestors = new Set) => {
6478
+ if (!isRecord(value) || value.kind !== "page" || !isContributionRef(value.page, "page"))
6479
+ return false;
6480
+ if (ancestors.has(value))
6481
+ return false;
6482
+ if (value.resource !== undefined && !isResource(value.resource))
6483
+ return false;
6484
+ if (value.section !== undefined && !isRecord(value.section))
6485
+ return false;
6486
+ if (!isOpenIntent(value.open))
6487
+ return false;
6488
+ if (value.parent === undefined)
6489
+ return true;
6490
+ const next = new Set(ancestors);
6491
+ next.add(value);
6492
+ return isPageTarget(value.parent, next);
6493
+ };
6494
+ var isPanelTarget = (value) => {
6495
+ if (!isRecord(value) || value.kind !== "panel" || !isRecord(value.panel))
6496
+ return false;
6497
+ const panel = value.panel;
6498
+ const validPanel = isContributionRef(panel, "placement") || panel.kind === "page-slot" && isContributionRef(panel.page, "page") && typeof panel.id === "string";
6499
+ return validPanel && (value.resource === undefined || isResource(value.resource)) && isOpenIntent(value.open);
6500
+ };
6501
+ var isItemTarget = (value) => {
6502
+ if (!isRecord(value))
6503
+ return false;
6504
+ if (value.kind === "page")
6505
+ return isPageTarget(value);
6506
+ if (value.kind === "panel")
6507
+ return isPanelTarget(value);
6508
+ if (value.kind === "href")
6509
+ return isNavigationHref(value.href);
6510
+ return value.kind === "command" && isRecord(value.target) && isContributionRef(value.target.command, "command") && (value.target.params === undefined || isRecord(value.target.params));
6511
+ };
6512
+ var isNavigationTarget = (value) => {
6513
+ if (isItemTarget(value))
6514
+ return true;
6515
+ if (!isRecord(value) || value.kind !== "compound" || !Array.isArray(value.targets) || value.targets.length === 0) {
6516
+ return false;
6517
+ }
6518
+ return value.targets.every((target) => isPageTarget(target) || isPanelTarget(target));
6519
+ };
6520
+ var withExtensionOwner = (ref, extensionId) => ref.extensionId !== undefined || extensionId === undefined ? ref : { ...ref, extensionId };
6521
+ var toPageTarget = (target, extensionId, projectId) => ({
6522
+ ...target,
6523
+ page: withExtensionOwner(target.page, extensionId),
6524
+ ...target.resource ? { resource: qualifyResource(target.resource, extensionId, projectId) } : {},
6525
+ ...target.parent ? { parent: toPageTarget(target.parent, extensionId, projectId) } : {}
6526
+ });
6527
+ var qualifyResource = (resource, extensionId, projectId) => ({
6528
+ ...resource,
6529
+ extensionId: resource.extensionId ?? extensionId,
6530
+ projectId: resource.projectId ?? projectId
6531
+ });
6532
+ var qualifyNavigationTarget = (target, extensionId, projectId) => {
6533
+ if (!isNavigationTarget(target))
6534
+ throw new Error("Invalid navigation target.");
6535
+ if (target.kind === "page")
6536
+ return toPageTarget(target, extensionId, projectId);
6537
+ if (target.kind === "panel")
6538
+ return {
6539
+ ...target,
6540
+ panel: target.panel.kind === "page-slot" ? { ...target.panel, page: withExtensionOwner(target.panel.page, extensionId) } : withExtensionOwner(target.panel, extensionId),
6541
+ ...target.resource ? { resource: qualifyResource(target.resource, extensionId, projectId) } : {}
6542
+ };
6543
+ if (target.kind === "command")
6544
+ return { ...target, target: { ...target.target, command: withExtensionOwner(target.target.command, extensionId) } };
6545
+ if (target.kind === "href")
6546
+ return target;
6547
+ return {
6548
+ ...target,
6549
+ targets: target.targets.map((item) => qualifyNavigationTarget(item, extensionId, projectId))
6550
+ };
6551
+ };
6456
6552
  // ../pstdio-api-contracts/src/extension-kernel/package-asset.ts
6457
6553
  var packageAsset = (path, baseUrl) => ({
6458
6554
  kind: "package-asset",
@@ -1,5 +1,6 @@
1
1
  // src/testing/memory-resources.ts
2
2
  var createMemoryResources = (prefixes, sequences = new Map) => ({
3
+ removed: async () => {},
3
4
  allocate: async ({ kind }) => {
4
5
  const prefix = prefixes[kind];
5
6
  if (!prefix)
@@ -28,6 +29,7 @@ var makeCommandContext = (input) => {
28
29
  project: { id: projectId, name: "Test Project", shorthand: "T" },
29
30
  storage,
30
31
  resources,
32
+ navigation: { open: () => {} },
31
33
  invocation: { params },
32
34
  events: { emit: async () => ({ delivered: 0 }) },
33
35
  notify: { action: async () => ({}), dismiss: async () => [], resolve: async () => [], toast: async () => {} },
@@ -57,6 +59,11 @@ var createMemoryRepoFiles = () => {
57
59
  writeText: async (path, value) => {
58
60
  files.set(path, encoder.encode(value));
59
61
  },
62
+ updateText: async (path, value) => {
63
+ if (!files.has(path))
64
+ throw new Error(`File not found: ${path}`);
65
+ files.set(path, encoder.encode(value));
66
+ },
60
67
  readBytes: async (path) => {
61
68
  const value = files.get(path);
62
69
  if (value === undefined)
@@ -170,6 +177,11 @@ var createMemoryStorage = () => {
170
177
  async put(id, value) {
171
178
  store.set(id, value);
172
179
  },
180
+ async update(id, value) {
181
+ if (!store.has(id))
182
+ throw new Error(`Item not found: ${id}`);
183
+ store.set(id, value);
184
+ },
173
185
  async createIfAbsent(id, value) {
174
186
  if (store.has(id))
175
187
  return false;
package/dist/testing.d.ts CHANGED
@@ -10,6 +10,8 @@ declare interface ArtifactMount {
10
10
  exists(path: string): Promise<boolean>;
11
11
  readText(path: string): Promise<string>;
12
12
  writeText(path: string, value: string): Promise<void>;
13
+ /** Replace existing text. Fails if missing and never recreates a concurrently deleted file. */
14
+ updateText(path: string, value: string): Promise<void>;
13
15
  readBytes(path: string): Promise<Uint8Array>;
14
16
  writeBytes(path: string, value: Uint8Array): Promise<void>;
15
17
  list(pattern?: string): Promise<ArtifactFile[]>;
@@ -88,6 +90,7 @@ declare type CommandOutcome<TResult = unknown> = {
88
90
  ok: true;
89
91
  status: "success";
90
92
  value: TResult;
93
+ navigationRequests?: NavigationTarget[];
91
94
  notices?: CommandNotice[];
92
95
  diagnostics?: CommandDiagnostic[];
93
96
  } | {
@@ -283,6 +286,10 @@ declare interface ExtensionContextBase<TSettings extends Record<string, unknown>
283
286
  source?: CommandSource;
284
287
  storage: ExtensionStorageApi;
285
288
  resources: ExtensionResourcesApi;
289
+ /** Records navigation for the invoking UI; headless execution opens nothing. */
290
+ navigation: {
291
+ open(target: NavigationTarget): void;
292
+ };
286
293
  artifacts: ExtensionArtifactApi;
287
294
  /** Working tree of the invocation's repo, scoped to its root. Absent for non-repo (event/hook) invocations. */
288
295
  repoFiles?: ArtifactMount;
@@ -394,6 +401,8 @@ declare interface ExtensionReposApi {
394
401
  }
395
402
 
396
403
  declare interface ExtensionResourcesApi {
404
+ /** Publish a committed removal to clients displaying this project resource. */
405
+ removed(resource: ResourceRef): Promise<void>;
397
406
  allocate(input: {
398
407
  kind: string;
399
408
  }): Promise<{
@@ -480,6 +489,8 @@ declare interface ExtensionStorageCollectionApi<TItem = unknown> {
480
489
  get(id: string): Promise<TItem | undefined>;
481
490
  list(): Promise<TItem[]>;
482
491
  put(id: string, value: TItem): Promise<void>;
492
+ /** Replace an existing item atomically. Throws if the item was deleted. */
493
+ update(id: string, value: TItem): Promise<void>;
483
494
  createIfAbsent(id: string, value: TItem): Promise<boolean>;
484
495
  deleteIfValue(id: string, value: TItem): Promise<boolean>;
485
496
  create(value: TItem): Promise<TItem & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pstdio/sdk",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/pufflyai/prompt-studio"