@arnilo/prism 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +8 -2
  3. package/dist/agents.js +21 -2
  4. package/dist/artifacts.d.ts +78 -0
  5. package/dist/artifacts.js +24 -0
  6. package/dist/contracts.d.ts +35 -1
  7. package/dist/contracts.js +8 -0
  8. package/dist/conversations.d.ts +50 -0
  9. package/dist/conversations.js +97 -0
  10. package/dist/credentials.d.ts +14 -0
  11. package/dist/credentials.js +9 -0
  12. package/dist/devices.d.ts +94 -0
  13. package/dist/devices.js +138 -0
  14. package/dist/extensions.d.ts +11 -0
  15. package/dist/extensions.js +15 -0
  16. package/dist/identity.d.ts +92 -0
  17. package/dist/identity.js +257 -0
  18. package/dist/index.d.ts +15 -5
  19. package/dist/index.js +8 -3
  20. package/dist/persistence-lifecycle.d.ts +103 -0
  21. package/dist/persistence-lifecycle.js +204 -0
  22. package/dist/providers/openai-compatible.d.ts +5 -1
  23. package/dist/providers/openai-compatible.js +15 -6
  24. package/dist/providers/openai-primitives.js +5 -2
  25. package/dist/secure-agent.js +7 -1
  26. package/dist/testing/persistence-schema.d.ts +2 -2
  27. package/dist/testing/persistence-schema.js +35 -2
  28. package/dist/tools.d.ts +2 -0
  29. package/dist/tools.js +6 -0
  30. package/docs/a2a.md +2 -0
  31. package/docs/ag-ui.md +5 -0
  32. package/docs/agent-identity.md +111 -0
  33. package/docs/browser-automation.md +3 -0
  34. package/docs/conversations.md +135 -0
  35. package/docs/credential-storage.md +31 -1
  36. package/docs/credentials-and-redaction.md +2 -0
  37. package/docs/database-persistence.md +22 -7
  38. package/docs/device-adapters.md +97 -0
  39. package/docs/extensions.md +1 -0
  40. package/docs/guardrails.md +3 -0
  41. package/docs/host-security.md +9 -3
  42. package/docs/index.md +26 -13
  43. package/docs/mcp-tools.md +2 -0
  44. package/docs/migration.md +48 -0
  45. package/docs/model-routing.md +102 -0
  46. package/docs/observability.md +2 -0
  47. package/docs/performance.md +21 -0
  48. package/docs/policy-and-audit.md +128 -0
  49. package/docs/postgres-persistence.md +1 -1
  50. package/docs/provider-caching.md +4 -0
  51. package/docs/provider-packages.md +12 -2
  52. package/docs/provider-request-policies.md +2 -0
  53. package/docs/providers/alibaba.md +179 -0
  54. package/docs/providers/azure.md +74 -0
  55. package/docs/providers/bedrock.md +72 -0
  56. package/docs/providers/google.md +1 -0
  57. package/docs/providers/ollama.md +166 -0
  58. package/docs/providers/openai-compatible.md +3 -1
  59. package/docs/providers/openrouter.md +2 -0
  60. package/docs/providers/vertex.md +71 -0
  61. package/docs/public-contracts.md +3 -1
  62. package/docs/release-and-install.md +149 -7
  63. package/docs/review-coverage-2026-07-23-phase-8.md +245 -0
  64. package/docs/review-coverage-2026-07-25-phase-9.md +256 -0
  65. package/docs/runs-and-usage.md +2 -0
  66. package/docs/server.md +37 -4
  67. package/docs/sqlite-persistence.md +1 -1
  68. package/docs/supervisors.md +2 -0
  69. package/docs/work-artifacts-and-review.md +100 -0
  70. package/docs/work-connectors.md +32 -0
  71. package/docs/work-tools.md +117 -0
  72. package/docs/workflows.md +4 -0
  73. package/docs/working-and-semantic-memory.md +20 -5
  74. package/package.json +4 -1
  75. package/templates/init/providers.json +22 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.14] - 2026-07-26
4
+
5
+ ### Added
6
+
7
+ - Phase 9 personal/work-agent surfaces: durable conversation service (`createConversationService`), durable artifact service with review/approval/authorized delivery (`createArtifactService`), memory consent + lifecycle (`setConsent`/`correct`/`forget`/`applyRetention`), AG-UI co-work events (`mapCoWork` + ACP parity), scoped M365/GWS OAuth connectors (`revokeOAuthCredential`, `createOAuthWorkTokenProvider`), a browser verified-state checkpoint ledger, and a deny-by-default device adapter contract (`resolveDevicePolicy`/`assertDeviceAdmit`).
8
+ - New optional provider packages `@arnilo/prism-provider-alibaba` (Model Studio / DashScope + Coding Plan) and `@arnilo/prism-provider-ollama` (cloud/local), both with dynamic model discovery; enrolled via `@arnilo/prism-providers`.
9
+
10
+ ### Changed
11
+
12
+ - Versioned all **43** first-party manifests and exact internal ranges to `0.0.14` (41 → 43; only the two provider packages are new).
13
+ - Network-free Phase 9 evidence: `scripts/benchmark-0.0.14.mjs`.
14
+
15
+ ## [0.0.13] - 2026-07-24
16
+
17
+ ### Added
18
+
19
+ - Enterprise identity (`Principal` / `AgentIdentity`), optional `@arnilo/prism-policy`, `@arnilo/prism-model-router`, enterprise cloud providers (Azure/Bedrock/Vertex), server deployment seams, persistence schema v5 lifecycle hooks, and `@arnilo/prism-work-tools` (M365 + GWS).
20
+
21
+ ### Changed
22
+
23
+ - Versioned all **41** first-party manifests and exact internal ranges to `0.0.13`; Phase 8 optional packages enroll in `@arnilo/prism-all` only.
24
+ - Network-free enterprise evidence: `scripts/benchmark-0.0.13.mjs`.
25
+
3
26
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
27
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
28
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ packages. Prism defines contracts, not apps.
17
17
  OpenAI/OpenRouter use best-effort explicit cache hints, NeuralWatt uses
18
18
  best-effort implicit prefix caching, and other providers have route/model-specific
19
19
  or no cache-control support; see [docs/provider-caching.md](docs/provider-caching.md).
20
- - **First-party packages**: six provider adapters, two compaction strategies,
20
+ - **First-party packages**: fourteen provider adapters, two compaction strategies,
21
21
  coding tools/security, JSON Schema validation, MCP, workflows, OpenTelemetry,
22
22
  encrypted credentials, SQLite/PostgreSQL persistence, and manifest-only install profiles.
23
23
  - **Tools, context, skills**: host-owned tool registry with allow/deny filtering
@@ -34,6 +34,10 @@ packages. Prism defines contracts, not apps.
34
34
  - **Config, settings, security**: layered config merge, settings providers,
35
35
  credential resolvers, trust/permission policies, and secret redaction.
36
36
  - **CLI/RPC/server**: `prism --mode print|json|rpc`, `prism init`, optional framework-free authorized Web agent/workflow routes, and explicit MCP server exposure.
37
+ - **Co-work contracts (0.0.14)**: conversation thread and artifact/review types, a
38
+ deny-by-default device-adapter contract (`resolveDevicePolicy`/`assertDeviceAdmit`),
39
+ and OAuth refresh/revoke helpers; services ship in `@arnilo/prism-server` and
40
+ `@arnilo/prism-credentials-node`.
37
41
 
38
42
  ## Install
39
43
 
@@ -150,6 +154,8 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
150
154
  | `@arnilo/prism-provider-zai` | ZAI GLM provider |
151
155
  | `@arnilo/prism-provider-kimi` | Kimi For Coding provider |
152
156
  | `@arnilo/prism-provider-neuralwatt` | NeuralWatt provider with implicit vLLM prefix caching |
157
+ | `@arnilo/prism-provider-alibaba` | Alibaba Cloud (Model Studio / DashScope + Coding Plan) provider with dynamic discovery and explicit/implicit caching |
158
+ | `@arnilo/prism-provider-ollama` | Ollama Cloud / local provider with dynamic discovery and implicit-only caching |
153
159
  | `@arnilo/prism-compaction-llm` | provider-backed compaction strategy |
154
160
  | `@arnilo/prism-compaction-observational-memory` | source-backed memory + recall tool |
155
161
  | `@arnilo/prism-coding-agent` | bounded shell/read/write/edit tools |
@@ -163,7 +169,7 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
163
169
  | `@arnilo/prism-credentials-node` | encrypted-file and keychain credentials |
164
170
  | `@arnilo/prism-session-store-sqlite` | SQLite persistence/checkpoints/leases/owned run feedback |
165
171
  | `@arnilo/prism-session-store-postgres` | PostgreSQL persistence/checkpoints/leases/owned run feedback |
166
- | `@arnilo/prism-providers` | family: all 7 provider adapters, including AI SDK interoperability |
172
+ | `@arnilo/prism-providers` | family: all 11 provider adapters, including AI SDK interoperability |
167
173
  | `@arnilo/prism-compaction` | family: both compaction strategies |
168
174
  | `@arnilo/prism-base` | profile: core + compaction + JSON Schema validation |
169
175
  | `@arnilo/prism-code` | profile: base + coding tools/security + MCP |
package/dist/agents.js CHANGED
@@ -17,6 +17,7 @@ import { createToolRegistry, dispatchToolCall } from "./tools.js";
17
17
  import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
18
18
  import { agentFingerprint, initialAgentRunState, loadAgentRunState, publicState, saveAgentRunState, validateRunStateOptions } from "./agent-run-state.js";
19
19
  import { resolveActiveSkills } from "./skills.js";
20
+ import { identityTelemetryAttributes, ownershipFromIdentity, resolveRunIdentity, } from "./identity.js";
20
21
  export function createAgent(config) {
21
22
  return {
22
23
  config,
@@ -154,6 +155,7 @@ class RuntimeAgentSession {
154
155
  activeProvider;
155
156
  activeLedger;
156
157
  activeOwnership;
158
+ activeIdentity;
157
159
  activeIdempotencyKey;
158
160
  activeGuardrails;
159
161
  activeMetadata;
@@ -261,6 +263,9 @@ class RuntimeAgentSession {
261
263
  this.activeRedactor = options.redactor ?? this.agent.config.redactor;
262
264
  this.activeLedger = options.runLedger ?? this.agent.config.runLedger;
263
265
  this.activeOwnership = options.ownership ?? this.agent.config.ownership;
266
+ this.activeIdentity = resolveRunIdentity(options.identity, this.agent.config.identity, this.activeOwnership);
267
+ if (this.activeIdentity && !this.activeOwnership)
268
+ this.activeOwnership = ownershipFromIdentity(this.activeIdentity);
264
269
  this.activeIdempotencyKey = options.idempotencyKey ?? this.agent.config.idempotencyKey;
265
270
  this.activeGuardrails = mergeGuardrails(this.agent.config.guardrails, options.guardrails);
266
271
  this.activeDurable = resumed ?? (durableOptions ? { options: durableOptions, version: 0 } : undefined);
@@ -272,7 +277,12 @@ class RuntimeAgentSession {
272
277
  let runStatus = "succeeded";
273
278
  const runUsage = createUsageAccumulator();
274
279
  let usage;
275
- const metadata = { ...this.agent.config.metadata, ...this.metadata, ...options.metadata };
280
+ const metadata = {
281
+ ...this.agent.config.metadata,
282
+ ...this.metadata,
283
+ ...options.metadata,
284
+ ...(this.activeIdentity ? identityTelemetryAttributes(this.activeIdentity) : {}),
285
+ };
276
286
  this.activeMetadata = metadata;
277
287
  const limits = new RunLimitTracker(resolvedLimits, {
278
288
  onExceeded: (breach) => {
@@ -434,7 +444,14 @@ class RuntimeAgentSession {
434
444
  dispatchToolCall: (call) => dispatchToolCall({
435
445
  call,
436
446
  registry,
437
- context: { sessionId: this.id, runId, toolCallId: call.id, signal: controller.signal, metadata },
447
+ context: {
448
+ sessionId: this.id,
449
+ runId,
450
+ toolCallId: call.id,
451
+ signal: controller.signal,
452
+ metadata,
453
+ identity: this.activeIdentity,
454
+ },
438
455
  middleware: this.agent.config.middleware,
439
456
  emit: (event) => this.emit(event),
440
457
  permission: this.agent.config.permission,
@@ -442,6 +459,7 @@ class RuntimeAgentSession {
442
459
  redactor: this.activeRedactor,
443
460
  ledger: this.activeLedger,
444
461
  ownership: this.activeOwnership,
462
+ identity: this.activeIdentity,
445
463
  guardrails: this.activeGuardrails,
446
464
  limitTracker: limits,
447
465
  beforeExecute: async (mediatedCall) => {
@@ -577,6 +595,7 @@ class RuntimeAgentSession {
577
595
  finally {
578
596
  this.activeLedger = undefined;
579
597
  this.activeOwnership = undefined;
598
+ this.activeIdentity = undefined;
580
599
  this.activeIdempotencyKey = undefined;
581
600
  this.activeGuardrails = undefined;
582
601
  this.activeMetadata = undefined;
@@ -0,0 +1,78 @@
1
+ import type { OwnershipScope } from "./contracts.js";
2
+ /**
3
+ * Durable artifact co-work review types (Phase 9 / 0.0.14). Core exports types only;
4
+ * the service + delivery-link signer live in `@arnilo/prism-server`. Prism persists bounded
5
+ * metadata, revisions, approvals, and delivery references — never file bodies (hosts own blobs).
6
+ */
7
+ /** Review state of an artifact's latest revision. */
8
+ export type ArtifactApprovalState = "pending" | "approved" | "rejected";
9
+ /** A resolved decision on one revision (pending is the absence of a decision). */
10
+ export type ArtifactDecisionState = Exclude<ArtifactApprovalState, "pending">;
11
+ /** Bounded citation / data-source reference. Host resolves the body; Prism stores the ref only. */
12
+ export interface ArtifactCitation {
13
+ readonly uri: string;
14
+ readonly title?: string;
15
+ /** Data-source kind (e.g. "web", "database", "upload"); host-defined, bounded. */
16
+ readonly kind?: string;
17
+ }
18
+ /** One immutable revision of an artifact. `uri`/`hash` reference host-owned content. */
19
+ export interface ArtifactRevision {
20
+ /** 1-based, monotonic within the artifact. */
21
+ readonly version: number;
22
+ /** Host-owned blob reference (redacted; never a local filesystem path). */
23
+ readonly uri: string;
24
+ readonly mime: string;
25
+ /** Host-computed content hash for integrity compare. */
26
+ readonly hash: string;
27
+ readonly changeNote?: string;
28
+ /** Run that produced this revision, if any. */
29
+ readonly producerRunId?: string;
30
+ readonly citations?: readonly ArtifactCitation[];
31
+ /** Preview metadata only; the host renders content. */
32
+ readonly preview?: Readonly<Record<string, unknown>>;
33
+ readonly createdAt: string;
34
+ }
35
+ /** A reviewer decision on a specific revision. */
36
+ export interface ArtifactApproval {
37
+ readonly version: number;
38
+ readonly state: ArtifactDecisionState;
39
+ /** Redacted reviewer actor reference. */
40
+ readonly reviewer: string;
41
+ /** Change-request / rejection note. */
42
+ readonly note?: string;
43
+ readonly decidedAt: string;
44
+ }
45
+ /**
46
+ * Durable artifact record. Stored as a versioned checkpoint value; the checkpoint version
47
+ * is the CAS counter for concurrent reviewers, distinct from revision numbers.
48
+ */
49
+ export interface ArtifactRecord extends OwnershipScope {
50
+ readonly id: string;
51
+ readonly threadId: string;
52
+ readonly title?: string;
53
+ readonly revisions: readonly ArtifactRevision[];
54
+ readonly approvals: readonly ArtifactApproval[];
55
+ /** Last approved revision; remains recoverable after a later rejection. */
56
+ readonly lastValidatedVersion?: number;
57
+ readonly createdAt: string;
58
+ readonly updatedAt: string;
59
+ }
60
+ /** Signed, expiring delivery authorization. Reauthorized per download; never a bearer secret. */
61
+ export interface ArtifactDeliveryToken extends OwnershipScope {
62
+ readonly artifactId: string;
63
+ readonly threadId: string;
64
+ readonly version: number;
65
+ readonly issuedAt: string;
66
+ readonly expiresAt: string;
67
+ }
68
+ /** Well-known checkpoint namespace for artifact records. */
69
+ export declare const ARTIFACT_CHECKPOINT_NAMESPACE = "prism.artifact";
70
+ export declare class ArtifactError extends Error {
71
+ readonly reason: string;
72
+ readonly code = "ERR_PRISM_ARTIFACT";
73
+ constructor(message: string, reason: string);
74
+ }
75
+ /** Checkpoint key for an artifact: thread-scoped so per-thread listing uses a key prefix. */
76
+ export declare function artifactCheckpointKey(threadId: string, artifactId: string): string;
77
+ /** Current review state: the decision on the latest revision, or pending when undecided. */
78
+ export declare function artifactApprovalState(record: ArtifactRecord): ArtifactApprovalState;
@@ -0,0 +1,24 @@
1
+ /** Well-known checkpoint namespace for artifact records. */
2
+ export const ARTIFACT_CHECKPOINT_NAMESPACE = "prism.artifact";
3
+ export class ArtifactError extends Error {
4
+ reason;
5
+ code = "ERR_PRISM_ARTIFACT";
6
+ constructor(message, reason) {
7
+ super(message);
8
+ this.reason = reason;
9
+ this.name = "ArtifactError";
10
+ }
11
+ }
12
+ /** Checkpoint key for an artifact: thread-scoped so per-thread listing uses a key prefix. */
13
+ export function artifactCheckpointKey(threadId, artifactId) {
14
+ return `${threadId}:${artifactId}`;
15
+ }
16
+ /** Current review state: the decision on the latest revision, or pending when undecided. */
17
+ export function artifactApprovalState(record) {
18
+ const latest = record.revisions[record.revisions.length - 1];
19
+ if (latest === undefined)
20
+ return "pending";
21
+ const decision = record.approvals.find((approval) => approval.version === latest.version);
22
+ return decision?.state ?? "pending";
23
+ }
24
+ //# sourceMappingURL=artifacts.js.map
@@ -287,6 +287,8 @@ export interface RunOptions {
287
287
  readonly redactor?: SecretRedactor;
288
288
  readonly runLedger?: RunLedger;
289
289
  readonly ownership?: OwnershipScope;
290
+ /** Host-verified identity; when set, must project onto `ownership` without widening. */
291
+ readonly identity?: import("./identity.js").AgentIdentity;
290
292
  readonly idempotencyKey?: string;
291
293
  readonly validate?: ToolValidator;
292
294
  readonly activeSkills?: readonly string[];
@@ -352,6 +354,8 @@ export interface AgentConfig {
352
354
  readonly redactor?: SecretRedactor;
353
355
  readonly runLedger?: RunLedger;
354
356
  readonly ownership?: OwnershipScope;
357
+ /** Host-verified identity default for sessions created from this agent. */
358
+ readonly identity?: import("./identity.js").AgentIdentity;
355
359
  readonly idempotencyKey?: string;
356
360
  readonly compaction?: false | CompactionOptions;
357
361
  readonly retry?: false | RetryOptions;
@@ -369,7 +373,7 @@ export interface AgentConfig {
369
373
  readonly secure?: true;
370
374
  }
371
375
  /** Opt-in fail-closed composition over the normal explicit AgentConfig API. */
372
- export interface SecureAgentOptions extends Omit<AgentConfig, "tools" | "validator" | "redactor" | "permission" | "trust" | "ownership" | "limits" | "runState" | "secure"> {
376
+ export interface SecureAgentOptions extends Omit<AgentConfig, "tools" | "validator" | "redactor" | "permission" | "trust" | "ownership" | "identity" | "limits" | "runState" | "secure"> {
373
377
  readonly id: string;
374
378
  readonly tools: readonly ToolDefinition[];
375
379
  readonly toolArgumentValidator: import("./tools.js").ToolArgumentValidator;
@@ -377,6 +381,8 @@ export interface SecureAgentOptions extends Omit<AgentConfig, "tools" | "validat
377
381
  readonly permission: PermissionPolicy;
378
382
  readonly trust: TrustPolicy;
379
383
  readonly ownership: OwnershipScope;
384
+ /** Optional host-verified identity; when set must match `ownership`. */
385
+ readonly identity?: import("./identity.js").AgentIdentity;
380
386
  readonly limits: RunLimits;
381
387
  readonly definitionRevision: string;
382
388
  readonly runState: Omit<AgentRunStateOptions, "definitionRevision" | "interruptBeforeTool">;
@@ -750,6 +756,8 @@ export interface ToolExecutionContext {
750
756
  readonly toolCallId: string;
751
757
  readonly signal?: AbortSignal;
752
758
  readonly metadata?: Readonly<Record<string, unknown>>;
759
+ /** Host-verified identity for this tool invocation, when enterprise identity is active. */
760
+ readonly identity?: import("./identity.js").AgentIdentity;
753
761
  progress?(progress?: unknown, metadata?: Readonly<Record<string, unknown>>): void | Promise<void>;
754
762
  }
755
763
  export interface ToolResult {
@@ -900,6 +908,9 @@ export interface ExtensionEvent {
900
908
  export interface Extension {
901
909
  readonly name: string;
902
910
  setup(api: ExtensionAPI): void | Promise<void>;
911
+ /** Host-attested signature/digest for `ExtensionLoadPolicy.verifySignature`. */
912
+ readonly signature?: string;
913
+ readonly metadata?: Readonly<Record<string, unknown>>;
903
914
  }
904
915
  export interface ProviderPackage {
905
916
  readonly name: string;
@@ -957,6 +968,8 @@ export interface OAuthProvider {
957
968
  readonly id: string;
958
969
  login(callbacks?: OAuthLoginCallbacks): Promise<OAuthCredentials> | OAuthCredentials;
959
970
  refresh?(credentials: OAuthCredentials): Promise<OAuthCredentials> | OAuthCredentials;
971
+ /** Best-effort upstream revocation; the store delete is what fails closed locally. */
972
+ revoke?(credentials: OAuthCredentials): Promise<void> | void;
960
973
  getCredential?(credentials: OAuthCredentials): Promise<Credential | undefined> | Credential | undefined;
961
974
  readonly metadata?: Readonly<Record<string, unknown>>;
962
975
  }
@@ -1482,16 +1495,21 @@ export interface MigrationRecord {
1482
1495
  }
1483
1496
  /** Query for sessions. */
1484
1497
  export interface SessionQuery extends PersistenceQuery, OwnershipScope {
1498
+ readonly id?: string;
1485
1499
  readonly parentSessionId?: string;
1486
1500
  readonly agentDefinitionId?: string;
1487
1501
  readonly agentDefinitionVersion?: string;
1488
1502
  readonly retentionPolicyId?: string;
1503
+ /** Match sessions whose `metadata` object contains this top-level key (e.g. conversation marker). */
1504
+ readonly metadataKey?: string;
1489
1505
  readonly fromCreatedAt?: string;
1490
1506
  readonly toCreatedAt?: string;
1491
1507
  readonly fromUpdatedAt?: string;
1492
1508
  readonly toUpdatedAt?: string;
1493
1509
  readonly hasExpired?: boolean;
1494
1510
  }
1511
+ /** Validate a top-level `SessionRecord.metadata` key used by `SessionQuery.metadataKey` filters. */
1512
+ export declare function assertSessionMetadataKey(key: string): string;
1495
1513
  /** Query for session entries. */
1496
1514
  export interface SessionEntryQuery extends PersistenceQuery, OwnershipScope {
1497
1515
  readonly sessionId?: string;
@@ -1602,9 +1620,17 @@ export interface ProductionPersistenceStore {
1602
1620
  queryAgentDefinitions(query: AgentDefinitionQuery): Promise<PersistencePage<AgentDefinitionRecord>>;
1603
1621
  queryRetentionPolicies(query: RetentionPolicyQuery): Promise<PersistencePage<RetentionPolicy>>;
1604
1622
  queryMigrations(query: MigrationQuery): Promise<PersistencePage<MigrationRecord>>;
1623
+ /** Optional session-record write capability (conversation threads, host-managed sessions).
1624
+ * Upserts by id; ownership columns are set on create, `metadata`/`updatedAt` on update. */
1625
+ appendSession?(record: SessionRecord): Promise<void>;
1605
1626
  /** DB-friendly branch read (mirrors `SessionStore.readBranchPath`): one ancestor-chain
1606
1627
  * query instead of `queryEntries({ sessionId })` + in-memory walk. Optional. */
1607
1628
  readBranchPath?(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>>;
1629
+ /**
1630
+ * Optional Phase 8 retention / legal-hold / export / tenant-quota lifecycle.
1631
+ * Prefer attaching `createMemoryPersistenceLifecycle()` or adapter-native methods.
1632
+ */
1633
+ readonly lifecycle?: import("./persistence-lifecycle.js").PersistenceLifecycleStore;
1608
1634
  readonly metadata?: Readonly<Record<string, unknown>>;
1609
1635
  }
1610
1636
  export interface CompactionStrategy {
@@ -1796,3 +1822,11 @@ export interface ArtifactParseResult<T> {
1796
1822
  export type ArtifactParser<T> = (text: string, ctx: ArtifactContext) => ArtifactParseResult<T> | Promise<ArtifactParseResult<T>>;
1797
1823
  export type ArtifactValidator<T> = (value: T, ctx: ArtifactContext) => ArtifactValidation | Promise<ArtifactValidation>;
1798
1824
  export type ArtifactRepairer<T> = (value: T | undefined, failure: ArtifactValidation, ctx: ArtifactContext) => AgentInput | Promise<AgentInput>;
1825
+ /** Alias re-exports so `dist/contracts.d.ts` exposes implementer contract names. */
1826
+ export type AgentIdentity = import("./identity.js").AgentIdentity;
1827
+ export type Principal = import("./identity.js").Principal;
1828
+ export type IdentityVerifier = import("./identity.js").IdentityVerifier;
1829
+ export type PersistenceLifecycleStore = import("./persistence-lifecycle.js").PersistenceLifecycleStore;
1830
+ export type LegalHoldRecord = import("./persistence-lifecycle.js").LegalHoldRecord;
1831
+ export type TenantQuota = import("./persistence-lifecycle.js").TenantQuota;
1832
+ export type PersistenceResourceKind = import("./persistence-lifecycle.js").PersistenceResourceKind;
package/dist/contracts.js CHANGED
@@ -128,4 +128,12 @@ export class SessionAppendConflictError extends Error {
128
128
  export function isSessionAppendConflict(error) {
129
129
  return error instanceof Error && error.code === SESSION_APPEND_CONFLICT_CODE;
130
130
  }
131
+ const SESSION_METADATA_KEY_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
132
+ /** Validate a top-level `SessionRecord.metadata` key used by `SessionQuery.metadataKey` filters. */
133
+ export function assertSessionMetadataKey(key) {
134
+ if (typeof key !== "string" || !SESSION_METADATA_KEY_PATTERN.test(key)) {
135
+ throw new RangeError("metadataKey must match /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/");
136
+ }
137
+ return key;
138
+ }
131
139
  //# sourceMappingURL=contracts.js.map
@@ -0,0 +1,50 @@
1
+ import type { OwnershipScope, SessionRecord } from "./contracts.js";
2
+ /** Conversation thread lifecycle state. Archive is soft; deletion goes through persistence lifecycle. */
3
+ export type ConversationThreadState = "active" | "archived";
4
+ /** Well-known `SessionRecord.metadata` key marking conversation threads. */
5
+ export declare const CONVERSATION_METADATA_KEY = "prismConversation";
6
+ export declare const DEFAULT_MAX_CONVERSATION_CURSOR_BYTES: number;
7
+ export declare const HARD_MAX_CONVERSATION_CURSOR_BYTES: number;
8
+ export interface ConversationBranchRef {
9
+ readonly leafId: string;
10
+ readonly createdAt: string;
11
+ }
12
+ /**
13
+ * Durable user-scoped conversation thread. A thread is an ownership-scoped session
14
+ * branch plus metadata; content lives in session entries and the redacted event ledger.
15
+ */
16
+ export interface ConversationThread extends OwnershipScope {
17
+ readonly id: string;
18
+ readonly title?: string;
19
+ readonly state: ConversationThreadState;
20
+ readonly createdAt: string;
21
+ readonly updatedAt: string;
22
+ /** Branch leaves recorded by the conversation service; the entry tree remains the content source of truth. */
23
+ readonly branches: readonly ConversationBranchRef[];
24
+ /** Host-supplied create metadata; never credentials or raw transcripts. */
25
+ readonly metadata?: Readonly<Record<string, unknown>>;
26
+ }
27
+ /** Opaque thread-bound replay cursor; prevents replaying a cursor minted for another thread. */
28
+ export interface ConversationReplayCursor {
29
+ readonly v: 1;
30
+ readonly threadId: string;
31
+ /** Opaque store keyset cursor; undefined means the start of the thread. */
32
+ readonly cursor?: string;
33
+ }
34
+ export declare class ConversationError extends Error {
35
+ readonly reason: string;
36
+ readonly code = "ERR_PRISM_CONVERSATION";
37
+ constructor(message: string, reason: string);
38
+ }
39
+ export declare function encodeConversationReplayCursor(cursor: ConversationReplayCursor): string;
40
+ export declare function decodeConversationReplayCursor(encoded: string, expectedThreadId: string, maxBytes?: number): ConversationReplayCursor;
41
+ /** Project a persisted session record into a conversation thread. Undefined for non-conversation sessions. */
42
+ export declare function conversationThreadFromRecord(record: SessionRecord): ConversationThread | undefined;
43
+ /** Serialize conversation marker metadata for `SessionRecord.metadata`. */
44
+ export declare function conversationMarkerMetadata(marker: {
45
+ readonly title?: string;
46
+ readonly state: ConversationThreadState;
47
+ readonly branches?: readonly ConversationBranchRef[];
48
+ readonly requestId?: string;
49
+ readonly metadata?: Readonly<Record<string, unknown>>;
50
+ }): Readonly<Record<string, unknown>>;
@@ -0,0 +1,97 @@
1
+ /** Well-known `SessionRecord.metadata` key marking conversation threads. */
2
+ export const CONVERSATION_METADATA_KEY = "prismConversation";
3
+ export const DEFAULT_MAX_CONVERSATION_CURSOR_BYTES = 4 * 1024;
4
+ export const HARD_MAX_CONVERSATION_CURSOR_BYTES = 16 * 1024;
5
+ export class ConversationError extends Error {
6
+ reason;
7
+ code = "ERR_PRISM_CONVERSATION";
8
+ constructor(message, reason) {
9
+ super(message);
10
+ this.reason = reason;
11
+ this.name = "ConversationError";
12
+ }
13
+ }
14
+ export function encodeConversationReplayCursor(cursor) {
15
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
16
+ }
17
+ export function decodeConversationReplayCursor(encoded, expectedThreadId, maxBytes = DEFAULT_MAX_CONVERSATION_CURSOR_BYTES) {
18
+ if (typeof encoded !== "string" || encoded.length === 0) {
19
+ throw new ConversationError("Replay cursor is required", "invalid_cursor");
20
+ }
21
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes) {
22
+ throw new ConversationError("Replay cursor exceeds byte limit", "cursor_too_large");
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
27
+ }
28
+ catch {
29
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
30
+ }
31
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
32
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
33
+ }
34
+ const cursor = parsed;
35
+ if (cursor.v !== 1 || typeof cursor.threadId !== "string" || cursor.threadId.length === 0) {
36
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
37
+ }
38
+ if (cursor.threadId !== expectedThreadId) {
39
+ throw new ConversationError("Replay cursor belongs to another thread", "cursor_thread_mismatch");
40
+ }
41
+ if (cursor.cursor !== undefined && (typeof cursor.cursor !== "string" || cursor.cursor.length === 0)) {
42
+ throw new ConversationError("Replay cursor is invalid", "invalid_cursor");
43
+ }
44
+ return {
45
+ v: 1,
46
+ threadId: cursor.threadId,
47
+ ...(cursor.cursor === undefined ? {} : { cursor: cursor.cursor }),
48
+ };
49
+ }
50
+ /** Project a persisted session record into a conversation thread. Undefined for non-conversation sessions. */
51
+ export function conversationThreadFromRecord(record) {
52
+ const marker = record.metadata?.[CONVERSATION_METADATA_KEY];
53
+ if (!marker || typeof marker !== "object" || Array.isArray(marker))
54
+ return undefined;
55
+ const value = marker;
56
+ const branches = [];
57
+ if (Array.isArray(value.branches)) {
58
+ for (const item of value.branches) {
59
+ if (item && typeof item === "object" &&
60
+ typeof item.leafId === "string" &&
61
+ typeof item.createdAt === "string") {
62
+ branches.push({
63
+ leafId: item.leafId,
64
+ createdAt: item.createdAt,
65
+ });
66
+ }
67
+ }
68
+ }
69
+ const metadata = value.metadata && typeof value.metadata === "object" && !Array.isArray(value.metadata)
70
+ ? value.metadata
71
+ : undefined;
72
+ return {
73
+ id: record.id,
74
+ ...(record.tenantId !== undefined ? { tenantId: record.tenantId } : {}),
75
+ ...(record.accountId !== undefined ? { accountId: record.accountId } : {}),
76
+ ...(record.userId !== undefined ? { userId: record.userId } : {}),
77
+ ...(typeof value.title === "string" && value.title.length > 0 ? { title: value.title } : {}),
78
+ state: value.state === "archived" ? "archived" : "active",
79
+ createdAt: record.createdAt,
80
+ updatedAt: record.updatedAt,
81
+ branches: Object.freeze(branches),
82
+ ...(metadata === undefined ? {} : { metadata }),
83
+ };
84
+ }
85
+ /** Serialize conversation marker metadata for `SessionRecord.metadata`. */
86
+ export function conversationMarkerMetadata(marker) {
87
+ return {
88
+ [CONVERSATION_METADATA_KEY]: {
89
+ ...(marker.title === undefined ? {} : { title: marker.title }),
90
+ state: marker.state,
91
+ ...(marker.branches === undefined || marker.branches.length === 0 ? {} : { branches: marker.branches }),
92
+ ...(marker.requestId === undefined ? {} : { requestId: marker.requestId }),
93
+ ...(marker.metadata === undefined ? {} : { metadata: marker.metadata }),
94
+ },
95
+ };
96
+ }
97
+ //# sourceMappingURL=conversations.js.map
@@ -19,4 +19,18 @@ export declare function refreshOAuthCredential(options: {
19
19
  readonly credentials: OAuthCredentials;
20
20
  readonly store?: OAuthCredentialStore;
21
21
  }): Promise<OAuthCredentials>;
22
+ /** A credential store that can also remove entries (revocation fails closed on the delete). */
23
+ export interface RevocableOAuthCredentialStore extends OAuthCredentialStore {
24
+ delete(provider: string, accountId?: string): Promise<boolean> | boolean;
25
+ }
26
+ /**
27
+ * Revokes an OAuth credential: best-effort upstream revocation, then a mandatory local
28
+ * store delete so subsequent connector calls fail closed. The local delete is the trust
29
+ * boundary — an upstream revoke failure never leaves a stored token usable.
30
+ */
31
+ export declare function revokeOAuthCredential(options: {
32
+ readonly provider: OAuthProvider;
33
+ readonly credentials: OAuthCredentials;
34
+ readonly store?: RevocableOAuthCredentialStore;
35
+ }): Promise<void>;
22
36
  export declare function resolveCredentialValue(source: CredentialValueSource | undefined, request: CredentialRequest): Promise<string | undefined>;
@@ -48,6 +48,15 @@ export async function refreshOAuthCredential(options) {
48
48
  await options.store?.set(options.provider.id, refreshed);
49
49
  return refreshed;
50
50
  }
51
+ /**
52
+ * Revokes an OAuth credential: best-effort upstream revocation, then a mandatory local
53
+ * store delete so subsequent connector calls fail closed. The local delete is the trust
54
+ * boundary — an upstream revoke failure never leaves a stored token usable.
55
+ */
56
+ export async function revokeOAuthCredential(options) {
57
+ await options.provider.revoke?.(options.credentials);
58
+ await options.store?.delete(options.provider.id, options.credentials.accountId);
59
+ }
51
60
  function credentialMapKey(name, provider) {
52
61
  return provider ? `${provider}:${name}` : name;
53
62
  }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Device adapter contract (realtime voice / desktop OS control) — 0.0.14.
3
+ *
4
+ * Contracts + deny-by-default policy ONLY. No vendor voice or desktop-control
5
+ * implementation ships in 0.0.14 (demand-gated to 0.1.x). This module composes
6
+ * over the existing `PermissionPolicy` / `RunLimits` / redactor seams; it adds
7
+ * no second approval runtime and no device framework. Hosts implement
8
+ * `DeviceAdapter`, resolve a policy, and admit sessions through the fail-closed
9
+ * gate below. Conformance fixtures (denial/approval/stream-bounds/redaction)
10
+ * live in `runDevicePolicyConformance` for future vendor adapters to run.
11
+ */
12
+ import type { RunLimits } from "./contracts.js";
13
+ import { type SecretRedactor } from "./redaction.js";
14
+ /** Audio / screenshot / stream chunk: 1 MiB default / 8 MiB hard. */
15
+ export declare const DEFAULT_DEVICE_MAX_CHUNK_BYTES: number;
16
+ export declare const HARD_DEVICE_MAX_CHUNK_BYTES: number;
17
+ /** Concurrent device sessions per identity: 1 default / 4 hard. */
18
+ export declare const DEFAULT_DEVICE_MAX_CONCURRENT_SESSIONS = 1;
19
+ export declare const HARD_DEVICE_MAX_CONCURRENT_SESSIONS = 4;
20
+ export type DeviceKind = "voice" | "desktop-control";
21
+ export type DevicePolicyErrorCode = "ERR_PRISM_DEVICE_INPUT" | "ERR_PRISM_DEVICE_DISABLED" | "ERR_PRISM_DEVICE_APPROVAL" | "ERR_PRISM_DEVICE_SESSIONS" | "ERR_PRISM_DEVICE_CHUNK" | "ERR_PRISM_DEVICE_RUN_LIMITS";
22
+ export declare class DevicePolicyError extends Error {
23
+ readonly code: DevicePolicyErrorCode;
24
+ constructor(code: DevicePolicyErrorCode, message: string);
25
+ }
26
+ export interface DeviceStreamLimits {
27
+ readonly maxChunkBytes?: number;
28
+ readonly maxConcurrentSessions?: number;
29
+ }
30
+ /**
31
+ * Host-declared device adapter. `enabled` is deny-by-default: a device is
32
+ * admitted only when the host explicitly sets it `true` AND supplies a sandbox
33
+ * AND (when `requireApproval`) explicit per-side-effect approval.
34
+ */
35
+ export interface DeviceAdapter {
36
+ readonly kind: DeviceKind;
37
+ readonly enabled: boolean;
38
+ readonly requireApproval: boolean;
39
+ readonly limits?: DeviceStreamLimits;
40
+ /** Host-owned sandbox identifier; admission fails closed without it. */
41
+ readonly sandbox?: string;
42
+ /** Host-owned network/egress policy identifier. */
43
+ readonly network?: string;
44
+ }
45
+ export interface ResolvedDevicePolicy {
46
+ readonly kind: DeviceKind;
47
+ readonly enabled: boolean;
48
+ readonly requireApproval: boolean;
49
+ readonly maxChunkBytes: number;
50
+ readonly maxConcurrentSessions: number;
51
+ readonly sandbox?: string;
52
+ readonly network?: string;
53
+ /** Shared run accounting the device session must consume. */
54
+ readonly runLimits?: RunLimits;
55
+ }
56
+ export interface DevicePolicyOptions {
57
+ readonly maxChunkBytes?: number;
58
+ readonly maxConcurrentSessions?: number;
59
+ readonly runLimits?: RunLimits;
60
+ }
61
+ export declare function resolveDevicePolicy(adapter: DeviceAdapter, options?: DevicePolicyOptions): ResolvedDevicePolicy;
62
+ export interface DeviceAdmitRequest {
63
+ /** Explicit host approval for this device side effect. */
64
+ readonly approved: boolean;
65
+ /** Currently active device sessions for this identity. */
66
+ readonly activeSessions: number;
67
+ }
68
+ /**
69
+ * Fail-closed admission gate. Denies unless the device is explicitly enabled,
70
+ * sandboxed, approved (when required), under the concurrent-session budget, and
71
+ * bound to shared run accounting. Side effects never replay after reconnect:
72
+ * hosts must re-admit on every resume.
73
+ */
74
+ export declare function assertDeviceAdmit(policy: ResolvedDevicePolicy, request: DeviceAdmitRequest): void;
75
+ export interface DeviceChunkResult {
76
+ readonly accepted: boolean;
77
+ readonly bytes: number;
78
+ /** Present when the chunk exceeded the stream bound and was dropped. */
79
+ readonly marker?: "dropped_oversize";
80
+ }
81
+ /** Stream bound: oversize audio/screenshot/stream chunks are dropped with a marker, never forwarded. */
82
+ export declare function acceptDeviceChunk(policy: ResolvedDevicePolicy, bytes: number): DeviceChunkResult;
83
+ /** Telemetry must be metadata-safe: apply the host redactor before any emit/persist. */
84
+ export declare function redactDeviceTelemetry<T>(redactor: SecretRedactor | undefined, telemetry: T): T;
85
+ export interface DeviceConformanceResult {
86
+ readonly passed: readonly string[];
87
+ }
88
+ /**
89
+ * Conformance pair for future voice / desktop-control adapters. Runs the
90
+ * deny-by-default fixtures (denial, approval, stream bounds, session budget,
91
+ * run accounting, redaction) against a resolved policy and throws on any
92
+ * regression. Tested in 0.0.14 via fixtures only.
93
+ */
94
+ export declare function runDevicePolicyConformance(adapter: DeviceAdapter, options?: DevicePolicyOptions): DeviceConformanceResult;