@openwop/openwop 1.1.3 → 1.1.6

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/src/client.ts CHANGED
@@ -22,6 +22,10 @@ import {
22
22
  type ErrorEnvelope,
23
23
  type ForkRunRequest,
24
24
  type ForkRunResponse,
25
+ type Annotation,
26
+ type CreateAnnotationRequest,
27
+ type WorkspaceFile,
28
+ type PutWorkspaceFileRequest,
25
29
  type GetPromptRequest,
26
30
  type InterruptByTokenInspection,
27
31
  type DebugBundle,
@@ -41,8 +45,24 @@ import {
41
45
  type ResolveInterruptResponse,
42
46
  type ResumeRunRequest,
43
47
  type ResumeRunResponse,
48
+ type RunAncestryResponse,
49
+ type RunDiffResponse,
44
50
  type RunEventDoc,
45
51
  type RunSnapshot,
52
+ type AgentInventoryEntry,
53
+ type AgentInventoryResponse,
54
+ type AgentRosterEntry,
55
+ type AgentRosterResponse,
56
+ type AgentOrgChart,
57
+ type OrgChartResponsibilityView,
58
+ type EvalSummary,
59
+ type AgentDeployment,
60
+ type AgentDeploymentTransition,
61
+ type CreateUserAgentRequest,
62
+ type UserAgentRecord,
63
+ type AgentPackRegistryResponse,
64
+ type InstallAgentPackRequest,
65
+ type InstallAgentPackResponse,
46
66
  } from './types.js';
47
67
 
48
68
  export interface OpenwopClientOptions {
@@ -142,7 +162,7 @@ export class OpenwopClient {
142
162
  } catch (err) {
143
163
  // Host doesn't advertise the capability → 404. Surface as null so callers
144
164
  // can branch on capability discovery without try/catch.
145
- if (err instanceof Error && /\b404\b/.test(err.message)) return null;
165
+ if (err instanceof WopError && err.status === 404) return null;
146
166
  throw err;
147
167
  }
148
168
  },
@@ -215,6 +235,105 @@ export class OpenwopClient {
215
235
  headers: this.#mutationHeaders(opts),
216
236
  }),
217
237
 
238
+ /**
239
+ * RFC 0056 — record a non-blocking quality annotation (rating / correction
240
+ * / label / flag) on a run/event/node. Returns the persisted `Annotation`.
241
+ * Throws on non-2xx (`501` when the host doesn't advertise
242
+ * `capabilities.feedback.supported`).
243
+ */
244
+ createAnnotation: (
245
+ runId: string,
246
+ body: CreateAnnotationRequest,
247
+ opts: MutationOptions = {},
248
+ ): Promise<Annotation> =>
249
+ this.#request<Annotation>({
250
+ method: 'POST',
251
+ path: `/v1/runs/${encodeURIComponent(runId)}/annotations`,
252
+ body,
253
+ headers: this.#mutationHeaders(opts),
254
+ }),
255
+
256
+ /**
257
+ * RFC 0056 — list a run's annotations (tenant-scoped). Returns `null` when
258
+ * the host doesn't advertise `capabilities.feedback` (404/501), so callers
259
+ * can branch on capability discovery without try/catch.
260
+ */
261
+ listAnnotations: async (runId: string): Promise<readonly Annotation[] | null> => {
262
+ try {
263
+ const res = await this.#request<{ annotations: Annotation[] }>({
264
+ method: 'GET',
265
+ path: `/v1/runs/${encodeURIComponent(runId)}/annotations`,
266
+ });
267
+ return res.annotations;
268
+ } catch (err) {
269
+ if (err instanceof WopError && (err.status === 404 || err.status === 501)) return null;
270
+ throw err;
271
+ }
272
+ },
273
+
274
+ /**
275
+ * RFC 0040 §C — fetch the run's immediate parent in the cross-host
276
+ * composition chain. Returns `parent: null` for top-level runs;
277
+ * `parent.wellKnownUrl` is set when the parent is on a different
278
+ * host, so callers walk the chain one hop at a time.
279
+ *
280
+ * Returns `null` when the host doesn't advertise
281
+ * `capabilities.multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported: true`
282
+ * (the endpoint returns 404 in that case per
283
+ * `spec/v1/multi-agent-execution.md` §"GET /v1/runs/{runId}/ancestry").
284
+ */
285
+ ancestry: async (runId: string): Promise<RunAncestryResponse | null> => {
286
+ try {
287
+ return await this.#request<RunAncestryResponse>({
288
+ method: 'GET',
289
+ path: `/v1/runs/${encodeURIComponent(runId)}/ancestry`,
290
+ });
291
+ } catch (err) {
292
+ if (err instanceof WopError && err.status === 404) return null;
293
+ throw err;
294
+ }
295
+ },
296
+
297
+ /**
298
+ * RFC 0054 — deterministic, replay-aware structured diff of two runs
299
+ * (typically a run and its `:fork`). Requires `runs:read` on BOTH
300
+ * `runId` and `against`. Returns `null` when the host doesn't
301
+ * implement the endpoint (404 per `spec/v1/rest-endpoints.md`
302
+ * §`GET /v1/runs/{runId}:diff`). `divergedAtSeq` is null + `eventDiffs`
303
+ * empty when the two logs are identical.
304
+ */
305
+ diff: async (runId: string, against: string): Promise<RunDiffResponse | null> => {
306
+ try {
307
+ return await this.#request<RunDiffResponse>({
308
+ method: 'GET',
309
+ path: `/v1/runs/${encodeURIComponent(runId)}:diff?against=${encodeURIComponent(against)}`,
310
+ });
311
+ } catch (err) {
312
+ if (err instanceof WopError && err.status === 404) return null;
313
+ throw err;
314
+ }
315
+ },
316
+
317
+ /**
318
+ * RFC 0081 §C — the `EvalSummary` scorecard for a terminal eval run (one
319
+ * started via `runs.create({ mode: 'eval', evalSuiteRef, agentId })`):
320
+ * aggregate + per-task scores, cost, latency, schema-validity, and
321
+ * redaction-safe safety findings. Returns `null` when the host doesn't
322
+ * advertise `capabilities.agents.evalSuite` or the run isn't an eval run
323
+ * (404). Throws `409` while the run is still in progress.
324
+ */
325
+ evalSummary: async (runId: string): Promise<EvalSummary | null> => {
326
+ try {
327
+ return await this.#request<EvalSummary>({
328
+ method: 'GET',
329
+ path: `/v1/runs/${encodeURIComponent(runId)}/eval-summary`,
330
+ });
331
+ } catch (err) {
332
+ if (err instanceof WopError && err.status === 404) return null;
333
+ throw err;
334
+ }
335
+ },
336
+
218
337
  pollEvents: (
219
338
  runId: string,
220
339
  params: { lastSequence?: number; timeoutSeconds?: number } = {},
@@ -242,6 +361,191 @@ export class OpenwopClient {
242
361
  streamEvents({ baseUrl: this.#baseUrl, apiKey: this.#apiKey }, runId, opts),
243
362
  };
244
363
 
364
+ // ── Manifest-agent inventory (RFC 0072 §A) ───────────────────────────
365
+ // Read-only. Gated on `capabilities.agents.manifestRuntime`; both methods
366
+ // return `null` when the host doesn't advertise it (the endpoints 404).
367
+ // Dispatch is not here: a manifest agent runs as a `runs.create` whose
368
+ // workflow node pins it via `WorkflowNode.agent` (RFC 0072 §B).
369
+ readonly agents = {
370
+ list: async (): Promise<AgentInventoryResponse | null> => {
371
+ try {
372
+ return await this.#request<AgentInventoryResponse>({ method: 'GET', path: '/v1/agents' });
373
+ } catch (err) {
374
+ if (err instanceof WopError && err.status === 404) return null;
375
+ throw err;
376
+ }
377
+ },
378
+
379
+ get: async (agentId: string): Promise<AgentInventoryEntry | null> => {
380
+ try {
381
+ return await this.#request<AgentInventoryEntry>({
382
+ method: 'GET',
383
+ path: `/v1/agents/${encodeURIComponent(agentId)}`,
384
+ });
385
+ } catch (err) {
386
+ if (err instanceof WopError && err.status === 404) return null;
387
+ throw err;
388
+ }
389
+ },
390
+
391
+ /**
392
+ * RFC 0082 §C/§E — list a manifest agent's deployment records (per-(agentId,
393
+ * version) lifecycle state + channels + canary + rollback pointer). Returns
394
+ * `null` when the host doesn't advertise `capabilities.agents.deployment`
395
+ * (the endpoint 404s).
396
+ */
397
+ listDeployments: async (agentId: string): Promise<readonly AgentDeployment[] | null> => {
398
+ try {
399
+ return await this.#request<AgentDeployment[]>({
400
+ method: 'GET',
401
+ path: `/v1/agents/${encodeURIComponent(agentId)}/deployments`,
402
+ });
403
+ } catch (err) {
404
+ if (err instanceof WopError && err.status === 404) return null;
405
+ throw err;
406
+ }
407
+ },
408
+
409
+ /**
410
+ * RFC 0082 §E — request a deployment state transition (promote / pause /
411
+ * deprecate / rollback / adjust-canary). The host authorizes fail-closed
412
+ * against the RFC 0049 `deploy:*` scope, runs any RFC 0051 approvalGate, and
413
+ * enforces RFC 0081 `requiredEval` before emitting `deployment.promoted`.
414
+ * Returns the updated deployment record. Throws on non-2xx (`403` fail-closed
415
+ * / `eval_gate_unmet`; `400` `no_active_deployment` / unsupported state).
416
+ */
417
+ transitionDeployment: (
418
+ agentId: string,
419
+ body: AgentDeploymentTransition,
420
+ opts: MutationOptions = {},
421
+ ): Promise<AgentDeployment> =>
422
+ this.#request<AgentDeployment>({
423
+ method: 'POST',
424
+ path: `/v1/agents/${encodeURIComponent(agentId)}/deployments`,
425
+ body,
426
+ headers: this.#mutationHeaders(opts),
427
+ }),
428
+
429
+ /**
430
+ * RFC 0086 §B — list the standing agent roster (named instances + their
431
+ * workflow portfolios) visible to the caller. Returns `null` when the host
432
+ * doesn't advertise `capabilities.agents.roster` (the endpoint 404s).
433
+ */
434
+ listRoster: async (): Promise<AgentRosterResponse | null> => {
435
+ try {
436
+ return await this.#request<AgentRosterResponse>({ method: 'GET', path: '/v1/agents/roster' });
437
+ } catch (err) {
438
+ if (err instanceof WopError && err.status === 404) return null;
439
+ throw err;
440
+ }
441
+ },
442
+
443
+ /**
444
+ * RFC 0086 §B — return one standing roster entry. Returns `null` on 404
445
+ * (no such entry, cross-tenant, or the capability is unadvertised).
446
+ */
447
+ getRosterEntry: async (rosterId: string): Promise<AgentRosterEntry | null> => {
448
+ try {
449
+ return await this.#request<AgentRosterEntry>({
450
+ method: 'GET',
451
+ path: `/v1/agents/roster/${encodeURIComponent(rosterId)}`,
452
+ });
453
+ } catch (err) {
454
+ if (err instanceof WopError && err.status === 404) return null;
455
+ throw err;
456
+ }
457
+ },
458
+
459
+ /**
460
+ * RFC 0087 §C — return the caller's agent org-chart (departments + roles +
461
+ * `reportsTo` over roster members; descriptive — confers no authority).
462
+ * Returns `null` when the host doesn't advertise `capabilities.agents.orgChart`.
463
+ */
464
+ getOrgChart: async (): Promise<AgentOrgChart | null> => {
465
+ try {
466
+ return await this.#request<AgentOrgChart>({ method: 'GET', path: '/v1/agents/org-chart' });
467
+ } catch (err) {
468
+ if (err instanceof WopError && err.status === 404) return null;
469
+ throw err;
470
+ }
471
+ },
472
+
473
+ /**
474
+ * RFC 0087 §D — one department's subtree + responsibility roll-up (the
475
+ * union of its members' RFC 0086 portfolios). `recursive: false` scopes the
476
+ * roll-up to direct members. Returns `null` on 404 (unknown/cross-tenant
477
+ * department, or the capability is unadvertised).
478
+ */
479
+ getOrgChartDepartment: async (
480
+ departmentId: string,
481
+ opts: { recursive?: boolean } = {},
482
+ ): Promise<OrgChartResponsibilityView | null> => {
483
+ const qs = opts.recursive === false ? '?recursive=false' : '';
484
+ try {
485
+ return await this.#request<OrgChartResponsibilityView>({
486
+ method: 'GET',
487
+ path: `/v1/agents/org-chart/${encodeURIComponent(departmentId)}${qs}`,
488
+ });
489
+ } catch (err) {
490
+ if (err instanceof WopError && err.status === 404) return null;
491
+ throw err;
492
+ }
493
+ },
494
+ };
495
+
496
+ // ── User-authored agents (sample-extension; non-normative) ───────────
497
+ // Backs the workflow-engine sample app's Agents tab. Pack-installed
498
+ // agents come through the `.agents` inventory above (RFC 0072 §A).
499
+ // These methods wrap the `POST/DELETE /v1/host/sample/agents` +
500
+ // `GET/POST /v1/host/sample/registry/agent-packs` host extensions.
501
+ // Returns `null` on 404 (capability absent — matches the `.agents`
502
+ // surface pattern); throws on other failures.
503
+ readonly userAgents = {
504
+ create: async (body: CreateUserAgentRequest, opts: MutationOptions = {}): Promise<UserAgentRecord> => {
505
+ return await this.#request<UserAgentRecord>({
506
+ method: 'POST',
507
+ path: '/v1/host/sample/agents',
508
+ body,
509
+ ...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
510
+ });
511
+ },
512
+
513
+ delete: async (agentId: string): Promise<boolean> => {
514
+ try {
515
+ await this.#request<void>({
516
+ method: 'DELETE',
517
+ path: `/v1/host/sample/agents/${encodeURIComponent(agentId)}`,
518
+ });
519
+ return true;
520
+ } catch (err) {
521
+ if (err instanceof WopError && err.status === 404) return false;
522
+ throw err;
523
+ }
524
+ },
525
+
526
+ listAvailablePacks: async (): Promise<AgentPackRegistryResponse | null> => {
527
+ try {
528
+ return await this.#request<AgentPackRegistryResponse>({
529
+ method: 'GET',
530
+ path: '/v1/host/sample/registry/agent-packs',
531
+ });
532
+ } catch (err) {
533
+ if (err instanceof WopError && err.status === 404) return null;
534
+ throw err;
535
+ }
536
+ },
537
+
538
+ installPack: async (
539
+ body: InstallAgentPackRequest,
540
+ ): Promise<InstallAgentPackResponse> => {
541
+ return await this.#request<InstallAgentPackResponse>({
542
+ method: 'POST',
543
+ path: '/v1/host/sample/registry/agent-packs/install',
544
+ body,
545
+ });
546
+ },
547
+ };
548
+
245
549
  // ── HITL interrupts (run-scoped + signed-token) ──────────────────────
246
550
  readonly interrupts = {
247
551
  resolveByRun: (
@@ -471,6 +775,94 @@ export class OpenwopClient {
471
775
  },
472
776
  };
473
777
 
778
+ // ── Agent workspace files (RFC 0059; gated on capabilities.workspace) ──
779
+ readonly workspace = {
780
+ /**
781
+ * RFC 0059 — list workspace file metadata (no bodies) for the caller's
782
+ * `{tenant, workspace}`. Optional `prefix` filters the flat `path`
783
+ * namespace. Returns `null` when the host doesn't advertise
784
+ * `capabilities.workspace.supported` (501), so callers can branch on
785
+ * capability discovery without try/catch.
786
+ */
787
+ listFiles: async (opts: { prefix?: string } = {}): Promise<readonly WorkspaceFile[] | null> => {
788
+ const search = new URLSearchParams();
789
+ if (opts.prefix !== undefined) search.set('prefix', opts.prefix);
790
+ const qs = search.toString();
791
+ try {
792
+ const res = await this.#request<{ files: WorkspaceFile[] }>({
793
+ method: 'GET',
794
+ path: `/v1/host/workspace/files${qs ? `?${qs}` : ''}`,
795
+ });
796
+ return res.files;
797
+ } catch (err) {
798
+ if (err instanceof WopError && err.status === 501) return null;
799
+ throw err;
800
+ }
801
+ },
802
+
803
+ /**
804
+ * RFC 0059 — read one workspace file. Pass `version` for a historical
805
+ * snapshot when `capabilities.workspace.versioned`. Returns `null` when
806
+ * the file is absent (404) or the host doesn't advertise the capability
807
+ * (501).
808
+ */
809
+ getFile: async (path: string, opts: { version?: number } = {}): Promise<WorkspaceFile | null> => {
810
+ const search = new URLSearchParams();
811
+ if (opts.version !== undefined) search.set('version', String(opts.version));
812
+ const qs = search.toString();
813
+ try {
814
+ return await this.#request<WorkspaceFile>({
815
+ method: 'GET',
816
+ path: `/v1/host/workspace/files/${encodeURIComponent(path)}${qs ? `?${qs}` : ''}`,
817
+ });
818
+ } catch (err) {
819
+ if (err instanceof WopError && (err.status === 404 || err.status === 501)) return null;
820
+ throw err;
821
+ }
822
+ },
823
+
824
+ /**
825
+ * RFC 0059 — atomic create/replace of a workspace file. Pass `ifMatch`
826
+ * (the file's current `etag`) for optimistic concurrency; a stale token
827
+ * throws a `WopError` with status `409` (`workspace_conflict`). Content
828
+ * beyond `capabilities.workspace.maxFileBytes` throws `413`
829
+ * (`workspace_too_large`). Returns the persisted `WorkspaceFile`.
830
+ */
831
+ putFile: (
832
+ path: string,
833
+ body: PutWorkspaceFileRequest,
834
+ opts: MutationOptions & { ifMatch?: string } = {},
835
+ ): Promise<WorkspaceFile> => {
836
+ const headers = this.#mutationHeaders(opts);
837
+ if (opts.ifMatch !== undefined) headers['If-Match'] = opts.ifMatch;
838
+ return this.#request<WorkspaceFile>({
839
+ method: 'PUT',
840
+ path: `/v1/host/workspace/files/${encodeURIComponent(path)}`,
841
+ body,
842
+ headers,
843
+ });
844
+ },
845
+
846
+ /**
847
+ * RFC 0059 — delete a workspace file. Returns `true` on success (`204`),
848
+ * `false` when the file is absent (404) or the host doesn't advertise the
849
+ * capability (501).
850
+ */
851
+ deleteFile: async (path: string, opts: MutationOptions = {}): Promise<boolean> => {
852
+ try {
853
+ await this.#request<void>({
854
+ method: 'DELETE',
855
+ path: `/v1/host/workspace/files/${encodeURIComponent(path)}`,
856
+ headers: this.#mutationHeaders(opts),
857
+ });
858
+ return true;
859
+ } catch (err) {
860
+ if (err instanceof WopError && (err.status === 404 || err.status === 501)) return false;
861
+ throw err;
862
+ }
863
+ },
864
+ };
865
+
474
866
  // ── Internals ────────────────────────────────────────────────────────
475
867
  #mutationHeaders(opts: MutationOptions): Record<string, string> {
476
868
  const h: Record<string, string> = {};
@@ -35,6 +35,7 @@ import type {
35
35
  AgentReasoningDeltaPayload,
36
36
  AgentToolCalledPayload,
37
37
  AgentToolReturnedPayload,
38
+ MemoryWrittenPayload,
38
39
  RunEventDoc,
39
40
  TypedRunEvent,
40
41
  } from './types.js';
@@ -130,6 +131,18 @@ export function isAgentDecided(
130
131
  );
131
132
  }
132
133
 
134
+ /** `memory.written` (RFC 0057). Narrows when `type` matches AND the payload
135
+ * carries the required `memoryRef` + `memoryId` identifier strings. */
136
+ export function isMemoryWritten(
137
+ ev: RunEventDoc,
138
+ ): ev is TypedRunEvent<MemoryWrittenPayload> {
139
+ return (
140
+ ev.type === 'memory.written' &&
141
+ hasStringField(ev.payload, 'memoryRef') &&
142
+ hasStringField(ev.payload, 'memoryId')
143
+ );
144
+ }
145
+
133
146
  // ─── High-level subscription helper ─────────────────────────────────────
134
147
 
135
148
  /** Returned by {@link subscribeToAgentReasoning}. Call to cancel the
package/src/index.ts CHANGED
@@ -42,17 +42,23 @@ export type {
42
42
  ResumeRunRequest,
43
43
  ResumeRunResponse,
44
44
  RunConfigurable,
45
+ RunDiffEventDiff,
46
+ RunDiffResponse,
45
47
  RunEventDoc,
46
48
  RunSnapshot,
47
49
  RunStatus,
48
50
  StreamMode,
49
51
  TypedRunEvent,
52
+ // RFC 0059 — agent workspace (spec/v1/agent-workspace.md)
53
+ WorkspaceFile,
54
+ PutWorkspaceFileRequest,
50
55
  AgentReasonedPayload,
51
56
  AgentReasoningDeltaPayload,
52
57
  AgentToolCalledPayload,
53
58
  AgentToolReturnedPayload,
54
59
  AgentHandoffPayload,
55
60
  AgentDecidedPayload,
61
+ MemoryWrittenPayload,
56
62
  // RFC 0027 + RFC 0028 — Prompt library (spec/v1/prompts.md)
57
63
  GetPromptRequest,
58
64
  ListPromptsRequest,
@@ -63,6 +69,33 @@ export type {
63
69
  PromptVariable,
64
70
  RenderPromptRequest,
65
71
  RenderPromptResponse,
72
+ // RFC 0072 §A — Manifest-agent inventory (GET /v1/agents).
73
+ // Consumed by the workflow-engine sample app's Agents tab + chat
74
+ // `@` mention picker (2026-05-28 mention-symbol swap).
75
+ AgentInventoryEntry,
76
+ AgentInventoryResponse,
77
+ // RFC 0081 — Agent evaluation (spec/v1/agent-evaluation.md). EvalSummary
78
+ // scorecard read via `client.runs.evalSummary(runId)` for a `mode:'eval'` run.
79
+ AgentModelClass,
80
+ EvalSafetyFinding,
81
+ EvalTaskResult,
82
+ EvalRegression,
83
+ EvalSummary,
84
+ // RFC 0082 — Agent deployment lifecycle (spec/v1/agent-deployment.md).
85
+ // `client.agents.listDeployments` / `transitionDeployment`.
86
+ DeploymentState,
87
+ AgentDeployment,
88
+ AgentDeploymentTransition,
89
+ // Sample-extension surface — user-authored agents
90
+ // (`POST /v1/host/sample/agents`) + agent-pack registry browser
91
+ // (`GET/POST /v1/host/sample/registry/agent-packs`). Non-normative;
92
+ // wraps host-extension routes the sample app authors against.
93
+ CreateUserAgentRequest,
94
+ UserAgentRecord,
95
+ AgentPackSummary,
96
+ AgentPackRegistryResponse,
97
+ InstallAgentPackRequest,
98
+ InstallAgentPackResponse,
66
99
  } from './types.js';
67
100
  export { streamEvents } from './sse.js';
68
101
  export type { EventsStreamContext, EventsStreamOptions } from './sse.js';
@@ -76,6 +109,7 @@ export {
76
109
  isAgentToolReturned,
77
110
  isAgentHandoff,
78
111
  isAgentDecided,
112
+ isMemoryWritten,
79
113
  subscribeToAgentReasoning,
80
114
  } from './event-helpers.js';
81
115
  export type {