@openwop/openwop-conformance 1.6.1 → 1.11.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/CHANGELOG.md +44 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +127 -0
- package/api/openapi.yaml +518 -1
- package/coverage.md +44 -2
- package/fixtures/conformance-run-duration-breach.json +33 -0
- package/fixtures/oauth-providers/synthetic.json +38 -0
- package/fixtures.md +29 -0
- package/package.json +1 -1
- package/schemas/README.md +22 -0
- package/schemas/agent-deployment-transition.schema.json +49 -0
- package/schemas/agent-deployment.schema.json +54 -0
- package/schemas/agent-eval-suite.schema.json +140 -0
- package/schemas/agent-inventory-response.schema.json +115 -0
- package/schemas/agent-manifest.schema.json +5 -0
- package/schemas/agent-org-chart.schema.json +82 -0
- package/schemas/agent-ref.schema.json +12 -2
- package/schemas/agent-roster-entry.schema.json +81 -0
- package/schemas/agent-roster-response.schema.json +21 -0
- package/schemas/ai-envelope.schema.json +28 -0
- package/schemas/artifact-type-pack-manifest.schema.json +160 -0
- package/schemas/budget-policy.schema.json +18 -0
- package/schemas/capabilities.schema.json +448 -4
- package/schemas/chat-card-pack-manifest.schema.json +158 -0
- package/schemas/credential-provenance.schema.json +18 -0
- package/schemas/envelopes/media.audio.schema.json +38 -0
- package/schemas/envelopes/media.file.schema.json +37 -0
- package/schemas/envelopes/media.image.schema.json +33 -0
- package/schemas/eval-summary.schema.json +92 -0
- package/schemas/heartbeat-evaluated.schema.json +14 -0
- package/schemas/heartbeat-state-changed.schema.json +14 -0
- package/schemas/node-pack-manifest.schema.json +33 -1
- package/schemas/org-chart-responsibility-view.schema.json +26 -0
- package/schemas/run-event-payloads.schema.json +380 -6
- package/schemas/run-event.schema.json +23 -0
- package/schemas/tool-descriptor.schema.json +63 -0
- package/schemas/trigger-subscription.schema.json +26 -0
- package/schemas/workflow-definition.schema.json +5 -0
- package/schemas/workspace-file-create.schema.json +20 -0
- package/schemas/workspace-file.schema.json +39 -0
- package/src/lib/agentLoop.ts +44 -0
- package/src/lib/agentRoster.ts +76 -0
- package/src/lib/agentRuntime.ts +45 -0
- package/src/lib/artifactTypes.ts +96 -0
- package/src/lib/cardPacks.ts +52 -0
- package/src/lib/discovery-capabilities.ts +50 -0
- package/src/lib/distillation.ts +38 -0
- package/src/lib/feedback.ts +3 -3
- package/src/lib/heartbeat.ts +31 -0
- package/src/lib/liveRuntime.ts +59 -0
- package/src/lib/memoryAttribution.ts +48 -0
- package/src/lib/profiles.ts +157 -0
- package/src/lib/runtimeRequires.ts +38 -0
- package/src/lib/safeFetch.ts +87 -0
- package/src/lib/subRunAttestation.ts +35 -0
- package/src/lib/toolHooks.ts +33 -0
- package/src/scenarios/agent-deployment-shape.test.ts +139 -0
- package/src/scenarios/agent-eval-suite-shape.test.ts +167 -0
- package/src/scenarios/agent-live-allowlist-enforced.test.ts +53 -0
- package/src/scenarios/agent-live-invocation-bracket.test.ts +98 -0
- package/src/scenarios/agent-live-runtime-shape.test.ts +98 -0
- package/src/scenarios/agent-live-structured-output.test.ts +58 -0
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +33 -0
- package/src/scenarios/agent-loop-stateful-resume.test.ts +28 -0
- package/src/scenarios/agent-loop-version5-shape.test.ts +41 -0
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +33 -0
- package/src/scenarios/agent-manifest-runtime.test.ts +85 -0
- package/src/scenarios/agent-org-chart-shape.test.ts +127 -0
- package/src/scenarios/agent-platform-profile.test.ts +158 -0
- package/src/scenarios/agent-roster-attribution.test.ts +179 -0
- package/src/scenarios/agent-roster-shape.test.ts +146 -0
- package/src/scenarios/ai-envelope-shape.test.ts +14 -18
- package/src/scenarios/aiEnvelope.capBreached.test.ts +2 -1
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +2 -1
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -1
- package/src/scenarios/approval-gate-flow.test.ts +4 -6
- package/src/scenarios/artifact-schema-compile-bounded.test.ts +126 -0
- package/src/scenarios/artifact-type-pack-install.test.ts +78 -0
- package/src/scenarios/artifact-type-pack-manifest-validation.test.ts +140 -0
- package/src/scenarios/artifact-type-store-without-render.test.ts +54 -0
- package/src/scenarios/audit-log-integrity.test.ts +3 -2
- package/src/scenarios/auth-api-key-rotation.test.ts +2 -1
- package/src/scenarios/auth-mtls.test.ts +2 -1
- package/src/scenarios/auth-oauth2-client-credentials.test.ts +2 -1
- package/src/scenarios/auth-oidc-user-bearer.test.ts +2 -1
- package/src/scenarios/auth-saml-profile.test.ts +2 -1
- package/src/scenarios/auth-scim-profile.test.ts +2 -1
- package/src/scenarios/authorization-fail-closed.test.ts +2 -1
- package/src/scenarios/authorization-roles-shape.test.ts +2 -1
- package/src/scenarios/budget-policy-shape.test.ts +136 -0
- package/src/scenarios/byok-auth-modes.test.ts +141 -0
- package/src/scenarios/chat-card-pack-execution.test.ts +56 -0
- package/src/scenarios/chat-card-pack-manifest-validation.test.ts +128 -0
- package/src/scenarios/commitment-fired.test.ts +83 -0
- package/src/scenarios/credential-payload-redaction.test.ts +2 -1
- package/src/scenarios/credentials-capability-shape.test.ts +2 -1
- package/src/scenarios/cross-engine-append-ordering.test.ts +2 -1
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +3 -2
- package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
- package/src/scenarios/deadletter-capability-shape.test.ts +2 -1
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +2 -1
- package/src/scenarios/distillation-index-roundtrip.test.ts +35 -0
- package/src/scenarios/distillation-secret-carryforward.test.ts +35 -0
- package/src/scenarios/distillation-shape.test.ts +41 -0
- package/src/scenarios/distillation-stable-archive.test.ts +37 -0
- package/src/scenarios/distillation-token-budget.test.ts +45 -0
- package/src/scenarios/egress-provenance-shape.test.ts +137 -0
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +4 -3
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +5 -4
- package/src/scenarios/envelope-reasoning-shape.test.ts +3 -2
- package/src/scenarios/envelope-refusal-shape.test.ts +3 -2
- package/src/scenarios/envelope-rendering-hint.test.ts +95 -0
- package/src/scenarios/envelope-retry-attempted.test.ts +2 -1
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +3 -2
- package/src/scenarios/exec-not-protocol-tier.test.ts +137 -0
- package/src/scenarios/experimental-tier-shape.test.ts +5 -4
- package/src/scenarios/fs-path-traversal.test.ts +2 -1
- package/src/scenarios/heartbeat-capability-shape.test.ts +35 -0
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +28 -0
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +43 -0
- package/src/scenarios/heartbeat-runtime-bound.test.ts +30 -0
- package/src/scenarios/http-client-ssrf.test.ts +10 -13
- package/src/scenarios/mcp-toolcall-redaction.test.ts +3 -2
- package/src/scenarios/media-url-inline-cap.test.ts +167 -0
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +54 -0
- package/src/scenarios/memory-attribution-no-content.test.ts +45 -0
- package/src/scenarios/memory-attribution-replay-stable.test.ts +60 -0
- package/src/scenarios/memory-attribution-shape.test.ts +28 -0
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +44 -0
- package/src/scenarios/memory-capability-model-shape.test.ts +186 -0
- package/src/scenarios/memory-compaction-event-emitted.test.ts +2 -1
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +2 -1
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +2 -1
- package/src/scenarios/memory-consolidation-idempotent.test.ts +77 -0
- package/src/scenarios/memory-consolidation-shape.test.ts +90 -0
- package/src/scenarios/model-capability-substituted.test.ts +2 -1
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +6 -5
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +4 -3
- package/src/scenarios/multi-region-idempotency.test.ts +10 -10
- package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +145 -0
- package/src/scenarios/oauth-capability-shape.test.ts +2 -1
- package/src/scenarios/oauth-connector-redaction.test.ts +2 -1
- package/src/scenarios/pause-resume.test.ts +3 -3
- package/src/scenarios/production-backpressure.test.ts +2 -2
- package/src/scenarios/production-retention-expiry.test.ts +2 -2
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +2 -1
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +2 -1
- package/src/scenarios/prompt-composed-trust-marker.test.ts +2 -1
- package/src/scenarios/prompt-end-to-end-events.test.ts +2 -1
- package/src/scenarios/prompt-list-and-fetch.test.ts +2 -1
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +2 -1
- package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +2 -1
- package/src/scenarios/prompt-pack-install.test.ts +2 -1
- package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +2 -1
- package/src/scenarios/prompt-render-deterministic.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +2 -1
- package/src/scenarios/prompt-template-shape.test.ts +2 -1
- package/src/scenarios/provider-usage.test.ts +2 -1
- package/src/scenarios/replay-divergence-at-refusal.test.ts +4 -3
- package/src/scenarios/replay-fork-arbitrary.test.ts +3 -1
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +2 -1
- package/src/scenarios/replayDeterminism.test.ts +3 -1
- package/src/scenarios/run-execution-bounds-shape.test.ts +133 -0
- package/src/scenarios/runtime-requires-install-gate.test.ts +92 -0
- package/src/scenarios/runtime-requires-shape.test.ts +134 -0
- package/src/scenarios/safefetch-behavior.test.ts +99 -0
- package/src/scenarios/safefetch-live-audit.test.ts +175 -0
- package/src/scenarios/sandbox-memory-cap.test.ts +2 -1
- package/src/scenarios/sandbox-mvp-behavior.test.ts +2 -1
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
- package/src/scenarios/sandbox-timeout-cap.test.ts +2 -1
- package/src/scenarios/scheduling-capability-shape.test.ts +2 -1
- package/src/scenarios/scheduling-cron-fires-once.test.ts +2 -1
- package/src/scenarios/secret-leakage-otel-attribute.test.ts +7 -6
- package/src/scenarios/spec-corpus-validity.test.ts +20 -4
- package/src/scenarios/subrun-approval-fail-closed.test.ts +33 -0
- package/src/scenarios/subrun-approval-gate.test.ts +35 -0
- package/src/scenarios/subrun-attestation-shape.test.ts +30 -0
- package/src/scenarios/subrun-checksum-stable.test.ts +43 -0
- package/src/scenarios/tool-descriptor-shape.test.ts +133 -0
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +39 -0
- package/src/scenarios/tool-hooks-content-free.test.ts +40 -0
- package/src/scenarios/tool-hooks-rate-limit.test.ts +32 -0
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +34 -0
- package/src/scenarios/tool-hooks-shape.test.ts +34 -0
- package/src/scenarios/trigger-bridge-shape.test.ts +135 -0
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +3 -10
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +2 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +2 -2
- package/src/scenarios/wasm-pack-load.test.ts +2 -2
- package/src/scenarios/wasm-pack-memory-cap.test.ts +3 -6
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +2 -2
- package/src/scenarios/workflow-primary-output-annotation.test.ts +142 -0
- package/src/scenarios/workspace-behavior.test.ts +134 -0
- package/src/scenarios/workspace-capability-shape.test.ts +73 -0
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +84 -0
- package/src/scenarios/x-openwop-form-pack-manifest.test.ts +155 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/trigger-subscription.schema.json",
|
|
4
|
+
"title": "TriggerSubscription",
|
|
5
|
+
"description": "RFC 0083 §B. A durable inbound-trigger subscription record (a webhook registration, a schedule, a queue consumer) with a standardized four-state machine layered over the existing per-source registration. Composes RFC 0052/0053/0017 + webhooks.md + RFC 0040 causation; the channel wire format stays a vendor extension (§E). Content-free of inbound payloads/credentials (SR-1).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["subscriptionId", "source", "state"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"subscriptionId": { "type": "string", "minLength": 1, "description": "Stable host-unique id for the subscription. Correlates the §C delivery events + the management surface." },
|
|
11
|
+
"source": { "type": "string", "enum": ["webhook", "schedule", "queue", "email", "form"], "description": "Which trigger source backs the subscription. Channels beyond these (Slack/Discord/SMS) bridge as a vendor extension by registering a subscription of the closest source kind (§E)." },
|
|
12
|
+
"state": { "type": "string", "enum": ["active", "paused", "failed", "dead-lettered"], "description": "The §B state. `active`: accepting + delivering; `paused`: retained, not delivering (operator-held); `failed`: delivery failing past policy (the webhooks.md circuit-breaker generalized); `dead-lettered`: terminal, deliveries routed to the RFC 0053 sink." },
|
|
13
|
+
"dedupEnabled": { "type": "boolean", "description": "When true, the host de-duplicates inbound events by `dedupKey` within the retention window (§C-1; the idempotency.md Layer-1 model applied to inbound triggers)." },
|
|
14
|
+
"retryPolicy": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"description": "Delivery retry policy (§C-2). On exhaustion the subscription/delivery transitions to `dead-lettered` (RFC 0053).",
|
|
18
|
+
"properties": {
|
|
19
|
+
"maxAttempts": { "type": "integer", "minimum": 1, "description": "Maximum delivery attempts before dead-lettering." },
|
|
20
|
+
"backoff": { "type": "string", "enum": ["none", "fixed", "exponential"], "description": "Backoff strategy between attempts." }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"webhookId": { "type": "string", "minLength": 1, "description": "MAY — for `source: \"webhook\"`, the existing webhooks.md register key (unchanged; the state machine layers over it)." },
|
|
24
|
+
"secretFingerprint": { "type": "string", "minLength": 1, "maxLength": 32, "description": "MAY — for `source: \"webhook\"`, an identifier for the signing secret (the `(webhookId, secretFingerprint)` register key). It MUST be a **salted or host-keyed, TRUNCATED** one-way digest (e.g. the first 8–16 hex of `HMAC(hostKey, secret)`) — NOT the raw secret (SR-1) and NOT a full unsalted `SHA256(secret)` (a full unsalted hash of a low-entropy secret is an offline brute-force / confirmation oracle). The `maxLength: 32` ceiling structurally rejects a full 64-hex digest." }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -138,6 +138,11 @@
|
|
|
138
138
|
},
|
|
139
139
|
"credentialsRef": { "type": "string" },
|
|
140
140
|
"settings": { "type": "object" },
|
|
141
|
+
"outputRole": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"enum": ["primary", "secondary"],
|
|
144
|
+
"description": "RFC 0065 — author hint that this terminal node's output is the workflow's primary deliverable (`primary`) or an auxiliary output (`secondary`). Advisory: hosts MUST execute the workflow identically regardless of value. Tooling MAY use the hint to pick which of N terminal nodes' outputs to surface as the run's canonical artifact. Unknown values + absent values fall back to default behavior (show all terminal outputs)."
|
|
145
|
+
},
|
|
141
146
|
"disabled": { "type": "boolean", "default": false },
|
|
142
147
|
"notes": { "type": "string" },
|
|
143
148
|
"groupId": { "type": "string" },
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/workspace-file-create.schema.json",
|
|
4
|
+
"title": "WorkspaceFileCreate",
|
|
5
|
+
"description": "RFC 0059. Request body for `PUT /v1/host/workspace/files/{path}` (atomic create/replace). The `path` is taken from the URL and MUST NOT be supplied here; the host assigns `version`, `etag`, and `updatedAt`. The client supplies only `content` and an optional `contentType`. Optimistic concurrency is expressed via the `If-Match` request header (the file's current `etag`), NOT a body field — a stale `If-Match` returns `409 workspace_conflict`. A `content` exceeding `capabilities.workspace.maxFileBytes` returns `workspace_too_large`. The response is a full `workspace-file.schema.json`.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["content"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"content": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "UTF-8 file body to persist. SR-1-redacted on write per RFC 0059 §E WSR-1."
|
|
13
|
+
},
|
|
14
|
+
"contentType": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"default": "text/markdown",
|
|
17
|
+
"description": "Advisory MIME type of `content`. Defaults to `text/markdown`."
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/workspace-file.schema.json",
|
|
4
|
+
"title": "WorkspaceFile",
|
|
5
|
+
"description": "RFC 0059. A versioned, tenant·workspace-scoped ground-truth file in the `host.workspace` store. Returned by `GET /v1/host/workspace/files/{path}` and `PUT …/{path}`; the `list` endpoint returns the same shape minus `content` (metadata only). `path` is a flat namespace with `/`-in-names (NOT nested directories) — no `..`, no leading `/` (the pattern enforces this). Writes are atomic and optimistically concurrent via the `etag` token (`If-Match`). When `capabilities.workspace.versioned: true`, prior versions are retrievable via `?version=N`. Content is SR-1-redacted on write (RFC 0059 §E WSR-1) — never the BYOK plaintext.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["path", "content", "version", "updatedAt"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"path": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$",
|
|
13
|
+
"description": "Workspace-relative path. Flat namespace with `/`-in-names; MUST NOT contain `..` or a leading `/` (the pattern forbids both). Stable identity of the file within the `{tenant, workspace}` scope."
|
|
14
|
+
},
|
|
15
|
+
"content": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "UTF-8 file body. SR-1-redacted on write per RFC 0059 §E WSR-1 — a value the run's BYOK vault resolved is persisted as `[REDACTED:<secretId>]`, never the plaintext."
|
|
18
|
+
},
|
|
19
|
+
"contentType": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"default": "text/markdown",
|
|
22
|
+
"description": "Advisory MIME type of `content`. Defaults to `text/markdown` (the ground-truth-file convention)."
|
|
23
|
+
},
|
|
24
|
+
"version": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"minimum": 1,
|
|
27
|
+
"description": "Monotonic version. 1 on first create; each successful `PUT` bumps it by one. The latest version is always retrievable; historical versions are best-effort up to `capabilities.workspace.maxVersions`."
|
|
28
|
+
},
|
|
29
|
+
"etag": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Opaque version token for `If-Match` optimistic concurrency. A `PUT` carrying a stale `If-Match` MUST be refused with `409 workspace_conflict`."
|
|
32
|
+
},
|
|
33
|
+
"updatedAt": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"format": "date-time",
|
|
36
|
+
"description": "ISO 8601 timestamp of the write that produced this version."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0061 stateful agent-loop-lifecycle
|
|
3
|
+
* (`multiAgent.executionModel.version: 5`) conformance scenarios. Lives in lib/
|
|
4
|
+
* (not a *.test.ts) so scenarios import it via `../lib/agentLoop.js`.
|
|
5
|
+
*/
|
|
6
|
+
import { driver } from './driver.js';
|
|
7
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
8
|
+
|
|
9
|
+
/** Reads `multiAgent.executionModel` from discovery (root-first per RFC 0073);
|
|
10
|
+
* null when the host advertises no execution model (treated as no support). */
|
|
11
|
+
export async function readExecutionModelCap(): Promise<Record<string, unknown> | null> {
|
|
12
|
+
const ma = await readCapabilityFamily<{ executionModel?: unknown }>('multiAgent');
|
|
13
|
+
const em = ma?.executionModel;
|
|
14
|
+
return em && typeof em === 'object' ? (em as Record<string, unknown>) : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** True when the host advertises `executionModel.version >= 5`. */
|
|
18
|
+
export function isVersion5(em: Record<string, unknown> | null): boolean {
|
|
19
|
+
return typeof em?.version === 'number' && (em.version as number) >= 5;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True when the host advertises `workspace.supported: true` (root-first per RFC 0073). */
|
|
23
|
+
export async function hasWorkspace(): Promise<boolean> {
|
|
24
|
+
const ws = await readCapabilityFamily<{ supported?: unknown }>('workspace');
|
|
25
|
+
return ws?.supported === true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface LoopResult {
|
|
29
|
+
decisions?: Array<{ iteration?: unknown }>;
|
|
30
|
+
workspaceVisible?: { atWriteTurn?: unknown; atNextTurn?: unknown };
|
|
31
|
+
resumedIteration?: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Drives one bounded stateful loop via the host-sample seam, or null
|
|
35
|
+
* (soft-skip) when the host doesn't expose it. The seam is host-extension
|
|
36
|
+
* surface specified in host-sample-test-seams.md §"Open seams":
|
|
37
|
+
* `POST /v1/host/sample/agentloop/run` returns the ordered
|
|
38
|
+
* `runOrchestrator.decided` payloads (each carrying `iteration`) the host
|
|
39
|
+
* would emit, plus optional workspace-visibility + resume reports. */
|
|
40
|
+
export async function invokeAgentLoop(body: Record<string, unknown>): Promise<LoopResult | null> {
|
|
41
|
+
const res = await driver.post('/v1/host/sample/agentloop/run', body);
|
|
42
|
+
if (res.status === 404 || res.status === 405) return null; // seam absent — soft-skip
|
|
43
|
+
return (res.json as LoopResult | undefined) ?? {};
|
|
44
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the RFC 0086 `agents.roster` conformance scenarios.
|
|
3
|
+
* Lives in lib/ (not a `*.test.ts`) so scenarios import it via
|
|
4
|
+
* `../lib/agentRoster.js`.
|
|
5
|
+
*
|
|
6
|
+
* Two surfaces:
|
|
7
|
+
* - the NORMATIVE read (`GET /v1/agents/roster[/{rosterId}]`, RFC 0086 §B),
|
|
8
|
+
* exercised black-box against any conformant host; and
|
|
9
|
+
* - the host-sample fire seam (`POST /v1/host/sample/roster/fire`), used to
|
|
10
|
+
* drive a portfolio trigger so the `roster.run.initiated` attribution +
|
|
11
|
+
* ordering can be asserted against the test event-log seam. The fire seam
|
|
12
|
+
* is OPTIONAL — scenarios soft-skip on 404/405 (the reference roster store
|
|
13
|
+
* is deferred per RFC 0086 §Conformance).
|
|
14
|
+
*
|
|
15
|
+
* @see RFCS/0086-standing-agent-roster-and-workflow-portfolio.md
|
|
16
|
+
* @see spec/v1/agent-roster.md
|
|
17
|
+
*/
|
|
18
|
+
import { driver } from './driver.js';
|
|
19
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
20
|
+
|
|
21
|
+
/** Reads `agents.roster` from discovery (root-first per RFC 0073); null when
|
|
22
|
+
* unadvertised. */
|
|
23
|
+
export async function readRosterCap(): Promise<Record<string, unknown> | null> {
|
|
24
|
+
const agents = await readCapabilityFamily<{ roster?: unknown }>('agents');
|
|
25
|
+
const r = agents?.roster;
|
|
26
|
+
return r && typeof r === 'object' ? (r as Record<string, unknown>) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RosterEntry {
|
|
30
|
+
rosterId?: string;
|
|
31
|
+
persona?: string;
|
|
32
|
+
agentRef?: { agentId?: string; version?: string; channel?: string };
|
|
33
|
+
workflows?: string[];
|
|
34
|
+
owner?: { tenantId?: string; workspaceId?: string };
|
|
35
|
+
[k: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RosterResponse {
|
|
39
|
+
roster?: RosterEntry[];
|
|
40
|
+
total?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** GET the NORMATIVE standing roster (RFC 0086 §B `GET /v1/agents/roster`);
|
|
44
|
+
* null when the host doesn't serve it (404/405/501). */
|
|
45
|
+
export async function listRoster(): Promise<RosterResponse | null> {
|
|
46
|
+
const res = await driver.get('/v1/agents/roster');
|
|
47
|
+
if (res.status === 404 || res.status === 405 || res.status === 501) return null;
|
|
48
|
+
return (res.json as RosterResponse | undefined) ?? {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** GET a single roster entry by id. Returns `{ status, entry }` so a caller can
|
|
52
|
+
* distinguish a 404 (cross-tenant / unknown) from a served entry. */
|
|
53
|
+
export async function getRosterEntry(
|
|
54
|
+
rosterId: string,
|
|
55
|
+
): Promise<{ status: number; entry: RosterEntry | undefined }> {
|
|
56
|
+
const res = await driver.get(`/v1/agents/roster/${encodeURIComponent(rosterId)}`);
|
|
57
|
+
return { status: res.status, entry: res.json as RosterEntry | undefined };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RosterFireResult {
|
|
61
|
+
runId?: string;
|
|
62
|
+
rosterId?: string;
|
|
63
|
+
triggerSubscriptionId?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Drive a portfolio trigger for a roster member via the host-sample fire seam.
|
|
67
|
+
* `asWorkItem:true` requests the RFC 0083 durable-work-item path (carries a
|
|
68
|
+
* `triggerSubscriptionId` + run `causationId`). Returns null when the seam is
|
|
69
|
+
* unwired (404/405). */
|
|
70
|
+
export async function fireRosterPortfolio(
|
|
71
|
+
body: { rosterId?: string; triggerSource?: string; asWorkItem?: boolean } = {},
|
|
72
|
+
): Promise<RosterFireResult | null> {
|
|
73
|
+
const res = await driver.post('/v1/host/sample/roster/fire', body);
|
|
74
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
75
|
+
return (res.json as RosterFireResult | undefined) ?? {};
|
|
76
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0070 agents.manifestRuntime conformance scenario.
|
|
3
|
+
* Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/agentRuntime.js`.
|
|
4
|
+
*/
|
|
5
|
+
import { driver } from './driver.js';
|
|
6
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
7
|
+
|
|
8
|
+
/** Reads `agents.manifestRuntime` from discovery (root-first per RFC 0073);
|
|
9
|
+
* null when unadvertised. */
|
|
10
|
+
export async function readManifestRuntimeCap(): Promise<Record<string, unknown> | null> {
|
|
11
|
+
const agents = await readCapabilityFamily<{ manifestRuntime?: unknown }>('agents');
|
|
12
|
+
const mr = agents?.manifestRuntime;
|
|
13
|
+
return mr && typeof mr === 'object' ? (mr as Record<string, unknown>) : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AgentInventory {
|
|
17
|
+
agents?: Array<{ agentId?: string; [k: string]: unknown }>;
|
|
18
|
+
total?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** GET the NORMATIVE manifest-agent inventory (RFC 0072 §A `GET /v1/agents`);
|
|
22
|
+
* null when the host doesn't advertise the capability (404/405/501). */
|
|
23
|
+
export async function listManifestAgents(): Promise<AgentInventory | null> {
|
|
24
|
+
const res = await driver.get('/v1/agents');
|
|
25
|
+
if (res.status === 404 || res.status === 405 || res.status === 501) return null;
|
|
26
|
+
return (res.json as AgentInventory | undefined) ?? {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DispatchResult {
|
|
30
|
+
agentId?: string;
|
|
31
|
+
status?: string;
|
|
32
|
+
toolSurface?: string[];
|
|
33
|
+
confidence?: number;
|
|
34
|
+
threshold?: number;
|
|
35
|
+
events?: Array<{ type?: string; agentId?: string; decision?: string; [k: string]: unknown }>;
|
|
36
|
+
result?: unknown;
|
|
37
|
+
error?: { code?: string; message?: string };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Dispatch one manifest-agent turn via the host-sample seam; null when absent. */
|
|
41
|
+
export async function dispatchAgent(agentId: string, body: Record<string, unknown>): Promise<DispatchResult | null> {
|
|
42
|
+
const res = await driver.post(`/v1/host/sample/agents/${encodeURIComponent(agentId)}/dispatch`, body);
|
|
43
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
44
|
+
return (res.json as DispatchResult | undefined) ?? {};
|
|
45
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the RFC 0071 host.artifactTypes conformance scenarios.
|
|
3
|
+
* Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/artifactTypes.js`.
|
|
4
|
+
*
|
|
5
|
+
* The behavioral scenarios drive a documented host-extension seam — hosts
|
|
6
|
+
* wiring RFC 0071 Phase 1 (artifact-type packs) expose:
|
|
7
|
+
*
|
|
8
|
+
* POST /v1/host/sample/artifacttypes/install
|
|
9
|
+
* body: { manifest: <artifact-type-pack manifest>,
|
|
10
|
+
* schemas: { "<artifactTypeId>": <JSON Schema> } }
|
|
11
|
+
* → 2xx { installed: true, artifactTypeIds: string[] }
|
|
12
|
+
*
|
|
13
|
+
* POST /v1/host/sample/artifacttypes/produce
|
|
14
|
+
* body: { artifactTypeId: string, payload: unknown }
|
|
15
|
+
* → 2xx {
|
|
16
|
+
* artifactId: string,
|
|
17
|
+
* registered: boolean, // matched an installed artifact-type pack
|
|
18
|
+
* validated: boolean, // payload checked against the pack schema
|
|
19
|
+
* stored: boolean, // persisted (host.artifactTypes.store)
|
|
20
|
+
* rendered: boolean, // a renderer ran (host.artifactTypes.render)
|
|
21
|
+
* runStatus: 'completed' | 'failed',
|
|
22
|
+
* artifactCreated?: { registered?: boolean, artifactType?: string } // the emitted event
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Either seam returning 404/405 means the host hasn't wired it → soft-skip.
|
|
26
|
+
*/
|
|
27
|
+
import { driver } from './driver.js';
|
|
28
|
+
|
|
29
|
+
interface DiscoveryDoc {
|
|
30
|
+
capabilities?: Record<string, unknown>;
|
|
31
|
+
// host.* capabilities may also appear top-level per host-capabilities.md §"The contract pattern".
|
|
32
|
+
[k: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Reads `host.artifactTypes` from discovery (capabilities block or top-level); null when unadvertised. */
|
|
36
|
+
export async function readArtifactTypesCap(): Promise<Record<string, unknown> | null> {
|
|
37
|
+
const res = await driver.get('/.well-known/openwop');
|
|
38
|
+
const doc = res.json as DiscoveryDoc | undefined;
|
|
39
|
+
const fromCaps = doc?.capabilities && typeof doc.capabilities === 'object'
|
|
40
|
+
? (doc.capabilities as Record<string, unknown>)['host.artifactTypes']
|
|
41
|
+
: undefined;
|
|
42
|
+
const cap = fromCaps ?? doc?.['host.artifactTypes'];
|
|
43
|
+
return cap && typeof cap === 'object' ? (cap as Record<string, unknown>) : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function artifactTypesSupported(cap: Record<string, unknown> | null): boolean {
|
|
47
|
+
return cap?.['supported'] === true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Installs an artifact-type pack via the host-sample seam, or null (soft-skip) when absent. */
|
|
51
|
+
export async function installArtifactTypePack(
|
|
52
|
+
manifest: unknown,
|
|
53
|
+
schemas: Record<string, unknown>,
|
|
54
|
+
): Promise<{ status: number; json: unknown } | null> {
|
|
55
|
+
const res = await driver.post('/v1/host/sample/artifacttypes/install', { manifest, schemas });
|
|
56
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
57
|
+
return { status: res.status, json: res.json };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Produces an artifact of `artifactTypeId` via the host-sample seam, or null (soft-skip) when absent. */
|
|
61
|
+
export async function produceArtifact(
|
|
62
|
+
artifactTypeId: string,
|
|
63
|
+
payload: unknown,
|
|
64
|
+
): Promise<{ status: number; json: Record<string, unknown> } | null> {
|
|
65
|
+
const res = await driver.post('/v1/host/sample/artifacttypes/produce', { artifactTypeId, payload });
|
|
66
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
67
|
+
return { status: res.status, json: (res.json ?? {}) as Record<string, unknown> };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A minimal valid artifact-type pack + schema pair the scenarios install. */
|
|
71
|
+
export function sampleArtifactTypePack() {
|
|
72
|
+
const artifactTypeId = 'vendor.conformance.note';
|
|
73
|
+
const manifest = {
|
|
74
|
+
kind: 'artifact-type',
|
|
75
|
+
name: 'vendor.conformance.notes',
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
engines: { openwop: '>=1.1' },
|
|
78
|
+
artifactTypes: [
|
|
79
|
+
{
|
|
80
|
+
artifactTypeId,
|
|
81
|
+
schemaVersion: 1,
|
|
82
|
+
schemaRef: 'schemas/note.schema.json',
|
|
83
|
+
rendering: { display: 'markdown' },
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
const schema = {
|
|
88
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
89
|
+
$id: 'https://h.example/schemas/artifacts/vendor.conformance.note.schema.json',
|
|
90
|
+
type: 'object',
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
required: ['title', 'body'],
|
|
93
|
+
properties: { title: { type: 'string' }, body: { type: 'string' } },
|
|
94
|
+
};
|
|
95
|
+
return { artifactTypeId, manifest, schema };
|
|
96
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the RFC 0071 Phase 2 host.chat.cardPacks conformance scenarios.
|
|
3
|
+
* Lives in lib/ so scenarios import it via `../lib/cardPacks.js`.
|
|
4
|
+
*
|
|
5
|
+
* Hosts wiring chat card packs expose a documented host-extension seam:
|
|
6
|
+
*
|
|
7
|
+
* POST /v1/host/sample/cardpacks/execute
|
|
8
|
+
* body: { cardTypeId: string, inputs: Record<string, unknown> }
|
|
9
|
+
* → 2xx {
|
|
10
|
+
* artifactId?: string,
|
|
11
|
+
* registered?: boolean, // output validated against the linked outputArtifactType
|
|
12
|
+
* validated?: boolean,
|
|
13
|
+
* contentTrust?: 'trusted' | 'untrusted', // the composed-envelope trust tag (R2)
|
|
14
|
+
* runStatus?: 'completed' | 'failed',
|
|
15
|
+
* artifactCreated?: { registered?: boolean, artifactType?: string }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* A 404/405 means the host hasn't wired the seam → soft-skip.
|
|
19
|
+
*/
|
|
20
|
+
import { driver } from './driver.js';
|
|
21
|
+
|
|
22
|
+
interface DiscoveryDoc {
|
|
23
|
+
capabilities?: Record<string, unknown>;
|
|
24
|
+
[k: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Reads `host.chat.cardPacks` (or the `host.chat` block's `cardPacks` facet) from discovery; null when unadvertised. */
|
|
28
|
+
export async function readCardPacksCap(): Promise<Record<string, unknown> | null> {
|
|
29
|
+
const res = await driver.get('/.well-known/openwop');
|
|
30
|
+
const doc = res.json as DiscoveryDoc | undefined;
|
|
31
|
+
const caps = doc?.capabilities && typeof doc.capabilities === 'object' ? (doc.capabilities as Record<string, unknown>) : undefined;
|
|
32
|
+
// Accept either a discrete `host.chat.cardPacks` key or a `cardPacks` facet under `host.chat`.
|
|
33
|
+
const direct = caps?.['host.chat.cardPacks'] ?? doc?.['host.chat.cardPacks'];
|
|
34
|
+
if (direct && typeof direct === 'object') return direct as Record<string, unknown>;
|
|
35
|
+
const chat = caps?.['host.chat'] ?? doc?.['host.chat'];
|
|
36
|
+
const facet = chat && typeof chat === 'object' ? (chat as Record<string, unknown>)['cardPacks'] : undefined;
|
|
37
|
+
return facet && typeof facet === 'object' ? (facet as Record<string, unknown>) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function cardPacksSupported(cap: Record<string, unknown> | null): boolean {
|
|
41
|
+
return cap?.['supported'] === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Executes a registered card via the host-sample seam, or null (soft-skip) when absent. */
|
|
45
|
+
export async function executeCard(
|
|
46
|
+
cardTypeId: string,
|
|
47
|
+
inputs: Record<string, unknown>,
|
|
48
|
+
): Promise<{ status: number; json: Record<string, unknown> } | null> {
|
|
49
|
+
const res = await driver.post('/v1/host/sample/cardpacks/execute', { cardTypeId, inputs });
|
|
50
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
51
|
+
return { status: res.status, json: (res.json ?? {}) as Record<string, unknown> };
|
|
52
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared root-first reader for `/.well-known/openwop` capability families.
|
|
3
|
+
*
|
|
4
|
+
* Per `spec/v1/capabilities.md` §"Document-root layout" (RFC 0073), every
|
|
5
|
+
* capability family (`agents`, `secrets`, `aiProviders`, `auth`, `memory`,
|
|
6
|
+
* `multiAgent`, `authorization`, …) is a property of the discovery document
|
|
7
|
+
* ROOT — `capabilities.schema.json` defines no `capabilities` wrapper property.
|
|
8
|
+
* A top-level `capabilities` object is a deprecated legacy shape; the suite
|
|
9
|
+
* reads the root first and falls back to a `capabilities.*` wrapper only
|
|
10
|
+
* through the v1.x migration window. Standardizes the ad-hoc dual-read that
|
|
11
|
+
* already existed (e.g. `aiEnvelope.capBreached.test.ts`,
|
|
12
|
+
* `ai-envelope-shape.test.ts`) into one accessor so every helper reads the
|
|
13
|
+
* same way.
|
|
14
|
+
*
|
|
15
|
+
* @see spec/v1/capabilities.md §"Document-root layout"
|
|
16
|
+
* @see RFCS/0073-capability-document-root-layout.md
|
|
17
|
+
*/
|
|
18
|
+
import { driver } from './driver.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read one capability family from an already-fetched discovery doc: document
|
|
22
|
+
* root first, deprecated `capabilities.*` wrapper as a fallback. Returns
|
|
23
|
+
* `undefined` when the family is advertised in neither location.
|
|
24
|
+
*/
|
|
25
|
+
export function capabilityFamily<T = Record<string, unknown>>(
|
|
26
|
+
doc: unknown,
|
|
27
|
+
name: string,
|
|
28
|
+
): T | undefined {
|
|
29
|
+
if (!doc || typeof doc !== 'object') return undefined;
|
|
30
|
+
const root = (doc as Record<string, unknown>)[name];
|
|
31
|
+
if (root !== undefined) return root as T;
|
|
32
|
+
const wrapper = (doc as { capabilities?: unknown }).capabilities;
|
|
33
|
+
if (wrapper && typeof wrapper === 'object') {
|
|
34
|
+
return (wrapper as Record<string, unknown>)[name] as T | undefined;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Fetch `/.well-known/openwop` and return one capability family (root-first,
|
|
41
|
+
* wrapper-fallback). `undefined` when the host returns non-200 or doesn't
|
|
42
|
+
* advertise the family.
|
|
43
|
+
*/
|
|
44
|
+
export async function readCapabilityFamily<T = Record<string, unknown>>(
|
|
45
|
+
name: string,
|
|
46
|
+
): Promise<T | undefined> {
|
|
47
|
+
const res = await driver.get('/.well-known/openwop');
|
|
48
|
+
if (res.status !== 200) return undefined;
|
|
49
|
+
return capabilityFamily<T>(res.json, name);
|
|
50
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0062 scheduled-memory-distillation ("dreams")
|
|
3
|
+
* conformance scenarios. Lives in lib/ (not a *.test.ts) so scenarios import it
|
|
4
|
+
* via `../lib/distillation.js`.
|
|
5
|
+
*/
|
|
6
|
+
import { driver } from './driver.js';
|
|
7
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
8
|
+
|
|
9
|
+
/** Reads `memory.distillation` from discovery (root-first per RFC 0073); null
|
|
10
|
+
* when the host advertises no distillation sub-block (treated as no support). */
|
|
11
|
+
export async function readDistillationCap(): Promise<Record<string, unknown> | null> {
|
|
12
|
+
const memory = await readCapabilityFamily<{ distillation?: unknown }>('memory');
|
|
13
|
+
const d = memory?.distillation;
|
|
14
|
+
return d && typeof d === 'object' ? (d as Record<string, unknown>) : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface DistillResult {
|
|
18
|
+
event?: { distillation?: { tokenBudget?: unknown; tokensUsed?: unknown; indexUpdated?: unknown } };
|
|
19
|
+
error?: unknown;
|
|
20
|
+
archiveChecksum?: unknown;
|
|
21
|
+
indexUpdated?: unknown;
|
|
22
|
+
indexFile?: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Drives one budgeted distillation run via the host-sample seam, or null
|
|
26
|
+
* (soft-skip) when the host doesn't expose it. The seam is host-extension
|
|
27
|
+
* surface specified in host-sample-test-seams.md §"Open seams":
|
|
28
|
+
* `POST /v1/host/sample/memory/distill` returns the `memory.compacted` event
|
|
29
|
+
* (with the additive RFC 0062 `distillation` sub-object) the host would emit,
|
|
30
|
+
* plus the stable archive's checksum. Returns the raw status alongside the
|
|
31
|
+
* body so scenarios can assert the `token_budget_exceeded` failure path. */
|
|
32
|
+
export async function invokeDistill(
|
|
33
|
+
body: Record<string, unknown>,
|
|
34
|
+
): Promise<{ status: number; body: DistillResult } | null> {
|
|
35
|
+
const res = await driver.post('/v1/host/sample/memory/distill', body);
|
|
36
|
+
if (res.status === 404 || res.status === 405) return null; // seam absent — soft-skip
|
|
37
|
+
return { status: res.status, body: (res.json as DistillResult | undefined) ?? {} };
|
|
38
|
+
}
|
package/src/lib/feedback.ts
CHANGED
|
@@ -19,10 +19,10 @@ export async function readFeedbackCap(): Promise<Record<string, unknown> | null>
|
|
|
19
19
|
return fb && typeof fb === 'object' ? (fb as Record<string, unknown>) : null;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const SEED_FIXTURE = 'conformance-
|
|
22
|
+
const SEED_FIXTURE = 'conformance-noop';
|
|
23
23
|
|
|
24
|
-
/** Seeds a run via the basic `conformance-
|
|
25
|
-
* the fixture isn't advertised or creation fails. */
|
|
24
|
+
/** Seeds a run via the basic `conformance-noop` fixture; null (soft-skip)
|
|
25
|
+
* when the fixture isn't advertised or creation fails. */
|
|
26
26
|
export async function seedRun(tenantId: string): Promise<string | null> {
|
|
27
27
|
if (!isFixtureAdvertised(SEED_FIXTURE)) return null;
|
|
28
28
|
const r = await driver.post('/v1/runs', { workflowId: SEED_FIXTURE, tenantId, inputs: {} });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0060 host.heartbeat conformance scenarios.
|
|
3
|
+
* Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/heartbeat.js`.
|
|
4
|
+
*/
|
|
5
|
+
import { driver } from './driver.js';
|
|
6
|
+
|
|
7
|
+
interface DiscoveryDoc {
|
|
8
|
+
capabilities?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Reads `capabilities.heartbeat` from discovery; null when unadvertised. */
|
|
12
|
+
export async function readHeartbeatCap(): Promise<Record<string, unknown> | null> {
|
|
13
|
+
const res = await driver.get('/.well-known/openwop');
|
|
14
|
+
const caps = (res.json as DiscoveryDoc | undefined)?.capabilities;
|
|
15
|
+
const hb = caps && typeof caps === 'object' ? (caps as Record<string, unknown>)['heartbeat'] : undefined;
|
|
16
|
+
return hb && typeof hb === 'object' ? (hb as Record<string, unknown>) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** True when the host advertises a working heartbeat surface. */
|
|
20
|
+
export function heartbeatSupported(cap: Record<string, unknown> | null): boolean {
|
|
21
|
+
return cap?.['supported'] === true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Drives one heartbeat tick via the host-sample seam, or null (soft-skip)
|
|
25
|
+
* when the host doesn't expose it. The seam is host-extension surface —
|
|
26
|
+
* hosts wiring RFC 0060 expose `POST /v1/host/sample/heartbeat/tick`. */
|
|
27
|
+
export async function tickHeartbeat(body: Record<string, unknown>): Promise<{ status: number; json: unknown } | null> {
|
|
28
|
+
const res = await driver.post('/v1/host/sample/heartbeat/tick', body);
|
|
29
|
+
if (res.status === 404 || res.status === 405) return null; // seam absent — soft-skip
|
|
30
|
+
return { status: res.status, json: res.json };
|
|
31
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the RFC 0077 `agents.liveRuntime` conformance scenarios.
|
|
3
|
+
* Lives in lib/ (not a `*.test.ts`) so scenarios import it via
|
|
4
|
+
* `../lib/liveRuntime.js`.
|
|
5
|
+
*
|
|
6
|
+
* RFC 0077 adds NO new endpoint — a live manifest invocation rides the existing
|
|
7
|
+
* run surface (agent as root of `POST /v1/runs`, a `WorkflowNode.agent` step, or
|
|
8
|
+
* a chat `@mention`) and brackets the existing `agent.*` family with
|
|
9
|
+
* `agent.invocation.started` / `agent.invocation.completed`. To drive one
|
|
10
|
+
* deterministically in conformance, the host exposes the OPTIONAL sample seam
|
|
11
|
+
* `POST /v1/host/sample/agents/live-invoke` returning `{ runId, invocationId }`;
|
|
12
|
+
* the bracketed events are read back via the test event-log seam. The seam is
|
|
13
|
+
* deferred per RFC 0077 §Conformance, so scenarios soft-skip on 404/405.
|
|
14
|
+
*
|
|
15
|
+
* @see RFCS/0077-agent-run-lifecycle-and-live-manifest-dispatch.md
|
|
16
|
+
* @see spec/v1/multi-agent-execution.md §"Live manifest dispatch"
|
|
17
|
+
*/
|
|
18
|
+
import { driver } from './driver.js';
|
|
19
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
20
|
+
|
|
21
|
+
/** Reads `agents.liveRuntime` from discovery (root-first per RFC 0073); null
|
|
22
|
+
* when unadvertised. */
|
|
23
|
+
export async function readLiveRuntimeCap(): Promise<Record<string, unknown> | null> {
|
|
24
|
+
const agents = await readCapabilityFamily<{ liveRuntime?: unknown }>('agents');
|
|
25
|
+
const lr = agents?.liveRuntime;
|
|
26
|
+
return lr && typeof lr === 'object' ? (lr as Record<string, unknown>) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LiveInvokeResult {
|
|
30
|
+
runId?: string;
|
|
31
|
+
invocationId?: string;
|
|
32
|
+
outcome?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Drive one live manifest invocation via the host-sample seam. Body fields:
|
|
37
|
+
* - `agentId` (optional): the manifest agent to invoke; host picks a default
|
|
38
|
+
* when omitted.
|
|
39
|
+
* - `source` (optional): `workflow-node` | `run-api` | `chat-mention`.
|
|
40
|
+
* - `returnSchemaRef` (optional) + `forceInvalidResult` (optional): exercise
|
|
41
|
+
* the §B step-6 structured-output enforcement — force a result that violates
|
|
42
|
+
* the handoff schema so a `structuredOutput` host fails the run.
|
|
43
|
+
* - `attemptTool` (optional): the id of a tool OUTSIDE the agent's
|
|
44
|
+
* `toolAllowlist` the invocation should attempt (the §F-1 allowlist floor).
|
|
45
|
+
* Returns null when the seam is unwired (404/405).
|
|
46
|
+
*/
|
|
47
|
+
export async function invokeLive(
|
|
48
|
+
body: {
|
|
49
|
+
agentId?: string;
|
|
50
|
+
source?: string;
|
|
51
|
+
returnSchemaRef?: string;
|
|
52
|
+
forceInvalidResult?: boolean;
|
|
53
|
+
attemptTool?: string;
|
|
54
|
+
} = {},
|
|
55
|
+
): Promise<LiveInvokeResult | null> {
|
|
56
|
+
const res = await driver.post('/v1/host/sample/agents/live-invoke', body);
|
|
57
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
58
|
+
return (res.json as LiveInvokeResult | undefined) ?? {};
|
|
59
|
+
}
|