@arnilo/prism 0.6.0 → 0.8.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 +79 -5
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +4 -0
- package/dist/agent-approval.js +5 -1
- package/dist/agent-definitions.js +1 -0
- package/dist/agent-run-lifecycle.js +39 -4
- package/dist/agent-run-state.d.ts +18 -0
- package/dist/agent-run-state.js +39 -9
- package/dist/agent-session/helpers.js +6 -1
- package/dist/agent-session/session/assemble.js +159 -7
- package/dist/agent-session/session/persist.d.ts +16 -0
- package/dist/agent-session/session/persist.js +64 -4
- package/dist/agent-session/session/provider-round.d.ts +3 -3
- package/dist/agent-session/session/provider-round.js +12 -6
- package/dist/agent-session/session/tool-round.js +5 -1
- package/dist/agent-session/session/types.d.ts +22 -1
- package/dist/agent-session/session.d.ts +16 -0
- package/dist/agent-session/session.js +42 -3
- package/dist/artifacts.d.ts +39 -1
- package/dist/artifacts.js +73 -0
- package/dist/attention-compiler.d.ts +121 -0
- package/dist/attention-compiler.js +479 -0
- package/dist/checkpoints.js +7 -11
- package/dist/cli-init.js +20 -6
- package/dist/context-budget.d.ts +20 -1
- package/dist/context-budget.js +10 -1
- package/dist/contracts-core/agent.d.ts +7 -0
- package/dist/contracts-core/attention.d.ts +66 -0
- package/dist/contracts-core/attention.js +2 -0
- package/dist/contracts-core/compaction.d.ts +59 -0
- package/dist/contracts-core/compaction.js +77 -1
- package/dist/contracts-core/content.d.ts +5 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/provider.d.ts +4 -0
- package/dist/contracts-core/run-limits.d.ts +2 -0
- package/dist/contracts-core.d.ts +1 -0
- package/dist/contracts-core.js +1 -0
- package/dist/contracts-protocol.d.ts +44 -3
- package/dist/contracts-run-state.d.ts +32 -5
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/host-composition.d.ts +91 -0
- package/dist/host-composition.js +279 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +7 -4
- package/dist/input.d.ts +13 -1
- package/dist/input.js +40 -1
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +2 -2
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +149 -0
- package/dist/secure-agent.d.ts +2 -0
- package/dist/secure-agent.js +6 -1
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/dist/tool-result-fold.d.ts +12 -0
- package/dist/tool-result-fold.js +13 -6
- package/dist/tools.d.ts +10 -0
- package/dist/tools.js +41 -0
- package/docs/acp-agent.md +42 -11
- package/docs/acp.md +2 -1
- package/docs/ag-ui.md +10 -3
- package/docs/agent-definitions.md +9 -1
- package/docs/agent-events.md +4 -1
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +8 -7
- package/docs/attention-compiler.md +272 -0
- package/docs/cli-rpc.md +4 -2
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-security.md +6 -3
- package/docs/coding-tools.md +0 -1
- package/docs/coding-workspaces.md +22 -0
- package/docs/compaction-and-retry.md +36 -4
- package/docs/compaction-observational-memory.md +63 -10
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +17 -2
- package/docs/conversations.md +1 -1
- package/docs/core.md +1 -1
- package/docs/dev-inspector.md +4 -0
- package/docs/device-adapters.md +1 -0
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +18 -10
- package/docs/documents.md +40 -11
- package/docs/durable-runs.md +87 -0
- package/docs/enterprise-postgres-state.md +6 -2
- package/docs/evaluations.md +168 -4
- package/docs/execution-timeline.md +186 -0
- package/docs/guardrails.md +33 -0
- package/docs/history/0.7.0-primitive-review.md +254 -0
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/migration-0.0.md +2 -2
- package/docs/history/release-handoffs.md +75 -1
- package/docs/host-compositions.md +149 -0
- package/docs/host-security.md +2 -2
- package/docs/hosted-sandboxes.md +94 -0
- package/docs/index.md +82 -45
- package/docs/input-and-prompt-assembly.md +1 -0
- package/docs/knowledge-sync.md +84 -0
- package/docs/language-intelligence.md +1 -1
- package/docs/live-testing.md +8 -3
- package/docs/mcp-tools.md +3 -1
- package/docs/memory-fabric.md +416 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/migrate-to-0.5.md +1 -1
- package/docs/migrate-to-0.6.md +1 -0
- package/docs/migrate-to-0.7.md +345 -0
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migration.md +43 -1
- package/docs/model-registry.md +12 -2
- package/docs/model-routing.md +79 -4
- package/docs/multi-agent-patterns.md +20 -6
- package/docs/observability.md +52 -1
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +14 -4
- package/docs/options-index.md +47 -3
- package/docs/peer-dependencies.md +12 -10
- package/docs/postgres-persistence.md +1 -1
- package/docs/process-sessions.md +3 -1
- package/docs/prompt-registry.md +1 -1
- package/docs/provider-caching.md +4 -2
- package/docs/provider-conformance.md +1 -1
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +22 -22
- package/docs/providers/bedrock.md +71 -7
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/providers/openai.md +1 -1
- package/docs/rag.md +24 -8
- package/docs/realtime-voice.md +87 -0
- package/docs/release-and-install.md +53 -45
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +17 -2
- package/docs/server.md +7 -3
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +7 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/supervisors.md +33 -5
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/thinking-and-reasoning.md +3 -1
- package/docs/tools.md +6 -5
- package/docs/web-tools.md +2 -1
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +14 -4
- package/docs/work-connectors.md +12 -10
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +50 -18
- package/docs/workflows.md +69 -1
- package/docs/working-and-semantic-memory.md +25 -14
- package/package.json +5 -3
- package/templates/README.md +2 -0
- package/templates/business-worker/README.md.tmpl +19 -0
- package/templates/business-worker/env.example.tmpl +1 -0
- package/templates/business-worker/gitignore.tmpl +11 -0
- package/templates/business-worker/manifest.json +12 -0
- package/templates/business-worker/package.json.tmpl +23 -0
- package/templates/business-worker/src/agent.ts.tmpl +92 -0
- package/templates/business-worker/src/index.ts.tmpl +13 -0
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
- package/templates/business-worker/tsconfig.json.tmpl +15 -0
- package/templates/personal-assistant/README.md.tmpl +18 -0
- package/templates/personal-assistant/env.example.tmpl +1 -0
- package/templates/personal-assistant/gitignore.tmpl +11 -0
- package/templates/personal-assistant/manifest.json +11 -0
- package/templates/personal-assistant/package.json.tmpl +23 -0
- package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
- package/templates/personal-assistant/src/index.ts.tmpl +13 -0
- package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
- package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
package/docs/work-connectors.md
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
# Work connectors
|
|
2
2
|
|
|
3
|
-
Least-privilege Microsoft 365 and Google Workspace connectors live in `@arnilo/prism-
|
|
3
|
+
Least-privilege Microsoft 365 and Google Workspace connectors live in `@arnilo/prism-work/connectors`.
|
|
4
4
|
|
|
5
5
|
## Principles
|
|
6
6
|
|
|
7
|
-
1. **Host-pinned binary** — Prism never downloads or shells an untrusted CLI path.
|
|
8
|
-
2. **Hard-coded
|
|
9
|
-
3. **Draft-then-approve** — mutations create a draft; side effects run only after host approval.
|
|
7
|
+
1. **Host-pinned binary or HTTP adapter** — Prism never downloads or shells an untrusted CLI path; HTTP adapters use fixed origins and pinned fetch.
|
|
8
|
+
2. **Hard-coded operation maps** — models choose typed tool args; they never supply command strings or request URLs.
|
|
9
|
+
3. **Draft-then-approve & durable resumption** — mutations create a draft with tracked revisions and payload digests; side effects run only after host approval binds to that exact revision; durable checkpoint persistence survives process restart.
|
|
10
10
|
4. **Idempotent retries** — `IdempotencyStore` keyed by identity + operation key.
|
|
11
11
|
5. **Isolated config** — per-identity `configDir` (CLI `HOME`); no credential argv.
|
|
12
|
-
6. **Shared result shapes** — mail/calendar/file/task list/get tools normalize onto `WorkMailMessage` / `WorkCalendarEvent` / `WorkFileItem` / `WorkTaskItem` without hiding provider-specific ops.
|
|
12
|
+
6. **Shared result shapes** — mail/calendar/file/task list/get tools normalize onto `WorkMailMessage` / `WorkCalendarEvent` / `WorkFileItem` / `WorkTaskItem` without hiding provider-specific ops. Binary file gets return only untrusted artifact/path metadata plus hash and byte length.
|
|
13
13
|
|
|
14
14
|
## Microsoft 365
|
|
15
15
|
|
|
16
|
-
See [Work tools](work-tools.md).
|
|
16
|
+
See [Work tools](work-tools.md). Adapters: `createMicrosoft365CliAdapter` or `createMicrosoft365HttpAdapter` from `@arnilo/prism-work/connectors`.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
The CLI adapter uses [@pnp/cli-microsoft365](https://pnp.github.io/cli-microsoft365/) commands such as `outlook message list|get`, `outlook mail send`, `outlook event list|add`, `file list|add|copy`, `spo file sharinglink add`. The HTTP adapter maps its fixed operation set to `graph.microsoft.com` through pinned fetch; tokens reach it only in `Authorization`. `m365_file_get` accepts only an item ID and downloads via fixed `/me/drive/items/{id}/content` into a scanned artifact and/or contained sandbox path; it never emits bytes to model context. Upload drafts accept a host path, artifact ref, or contained sandbox path and bind the content hash before approval. It requires direct Graph Drive-item URLs for one-request file list/upload/copy and rejects arbitrary SharePoint links. To Do / Planner / Teams remain capability-gated.
|
|
19
19
|
|
|
20
20
|
## Google Workspace
|
|
21
21
|
|
|
22
|
-
See [Work tools](work-tools.md).
|
|
22
|
+
See [Work tools](work-tools.md). Adapters: `createGoogleWorkspaceCliAdapter` or `createGoogleWorkspaceHttpAdapter` from `@arnilo/prism-work/connectors`.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
The CLI adapter uses [`@googleworkspace/cli` (`gws`)](https://github.com/googleworkspace/cli): `gmail users messages list|get`, `gmail +send`, `calendar events list|insert`, `drive files list|create`, `drive permissions create`, `tasks tasks *`, and capability-gated Docs/Sheets/Slides create and fixed update commands. The HTTP adapter maps the same typed operations to Gmail, Calendar, Drive, Tasks, Docs, Sheets, and Slides REST origins with `pinnedFetch`; `gws_file_get` accepts only an item ID and uses fixed `Drive files.get?alt=media`, returning only scanned artifact/path metadata. Its fixed allowlist excludes model-supplied URLs. Docs/Sheets/Slides updates accept only replace/insert text or string-matrix values, never a model-provided batch request array. Discovery `schema` and `auth`/`login`/`setup` are forbidden from Prism argv.
|
|
25
|
+
|
|
26
|
+
Drive **knowledge synchronization** (RAG import of file text + host-mapped ACL via `changes.list`) is not this CLI adapter. Use `createGoogleDriveConnector` / `syncKnowledge` from `@arnilo/prism-memory/rag` — see [Knowledge synchronization](knowledge-sync.md).
|
|
25
27
|
|
|
26
28
|
## Scoped OAuth establishment (0.0.14)
|
|
27
29
|
|
|
28
|
-
Hosts establish, refresh, and revoke scoped OAuth credentials for these workloads through the existing `OAuthProvider` / credential-store seams (`@arnilo/prism-core/credentials/node`): `createMicrosoft365OAuthProvider` / `createGoogleWorkspaceOAuthProvider` (PKCE + device code), least-privilege scope bundles per capability (`resolveMicrosoft365Scopes` / `resolveGoogleWorkspaceScopes`, read vs mutation). Connectors consume a per-identity token via a late-bound `tokenProvider
|
|
30
|
+
Hosts establish, refresh, and revoke scoped OAuth credentials for these workloads through the existing `OAuthProvider` / credential-store seams (`@arnilo/prism-core/credentials/node`): `createMicrosoft365OAuthProvider` / `createGoogleWorkspaceOAuthProvider` (PKCE + device code), least-privilege scope bundles per capability (`resolveMicrosoft365Scopes` / `resolveGoogleWorkspaceScopes`, read vs mutation). Connectors consume a per-identity token via a late-bound `tokenProvider`: CLI adapters inject it into env and HTTP adapters send it only as `Authorization` — never argv or model context; revocation fails closed. See [Credential storage](credential-storage.md) and [Work tools](work-tools.md).
|
|
29
31
|
|
|
30
32
|
## Out of scope
|
|
31
33
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Work sandbox (`@arnilo/prism-work/sandbox`)
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Host-built Docker image and in-process composition for document work. `WORK_SANDBOX_IMAGE` is a digest-pinned fixture (`name@sha256:<64-hex>`); hosts replace the zero digest after `docker build`. `createWorkComposition({ sandbox })` takes an injected `DisposableSandbox` (from `createDockerSandbox`, never forked here), copies its capability attestation, wires `createOfficeTools` filesystem mode plus `work_exec`, and optionally host-side `createWorkTools`. Connectors stay on the host. Default env has no `M365_*` / `GOOGLE_*` keys.
|
|
6
|
+
|
|
7
|
+
## When to use it
|
|
8
|
+
|
|
9
|
+
Use when Office parse/generate and Python/LibreOffice/poppler scripts must run inside a network-none container. Do **not** put Graph/Gmail tokens in the sandbox. Do not import `@arnilo/prism-coding-tools` from this package — the host constructs `createDockerSandbox({ image: WORK_SANDBOX_IMAGE, user: "65532:65532", network: { mode: "none" } })` and injects the session.
|
|
10
|
+
|
|
11
|
+
## Inputs / request
|
|
12
|
+
|
|
13
|
+
`createWorkComposition(options)`:
|
|
14
|
+
|
|
15
|
+
| Field | Meaning |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `sandbox` | Injected adapter with `execFile` and optional `readFile`/`writeFile`/`root`. Required. |
|
|
18
|
+
| `connectors?` | `WorkToolsOptions` for host-side M365/GWS tools. |
|
|
19
|
+
| `office?` | Extra `createOfficeTools` options (caps, artifacts, redactor). |
|
|
20
|
+
| `reader?` | Host-built `DocumentReader`; exposed on the composition, not turned into tools. |
|
|
21
|
+
| `filesystem?` | `WorkSandboxFilesystem` when the sandbox has no `readFile`/`writeFile`. |
|
|
22
|
+
| `env?` | Extra container env. `M365_*` and `GOOGLE_*` names throw. |
|
|
23
|
+
|
|
24
|
+
Image build context: `packages/prism-work` (`sandbox/Dockerfile` + `sandbox/soffice.sh` + `vendor/hermes-agent`). Vendored Hermes scripts land at `/opt/prism-work/skills`.
|
|
25
|
+
|
|
26
|
+
## Outputs / response / events
|
|
27
|
+
|
|
28
|
+
`{ tools, composition }`. `tools` always include `office_*` and `work_exec`. `composition.capabilities` is a frozen copy of the sandbox attestation (malformed metadata → every field `false`). `networkIsolated` is true only when the sandbox attests it — Docker reports that solely for `network: { mode: "none" }`. `composition.execFile` strips token env names and forces LibreOffice `-env:UserInstallation=file:///tmp/lo-profile` without `--accept` / macro flags.
|
|
29
|
+
|
|
30
|
+
## Request/response example
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createDockerSandbox } from "@arnilo/prism-coding-tools/security";
|
|
34
|
+
import { createWorkComposition, WORK_SANDBOX_IMAGE } from "@arnilo/prism-work/sandbox";
|
|
35
|
+
|
|
36
|
+
const sandbox = await createDockerSandbox({
|
|
37
|
+
docker: "/usr/bin/docker",
|
|
38
|
+
image: WORK_SANDBOX_IMAGE, // replace zeros with the host-built digest
|
|
39
|
+
sourceRoot: workdir,
|
|
40
|
+
user: "65532:65532",
|
|
41
|
+
network: { mode: "none" },
|
|
42
|
+
});
|
|
43
|
+
const { tools, composition } = createWorkComposition({ sandbox, connectors });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Implementation example
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { createWorkComposition, WORK_SANDBOX_IMAGE } from "@arnilo/prism-work/sandbox";
|
|
50
|
+
|
|
51
|
+
const { tools, composition } = createWorkComposition({
|
|
52
|
+
sandbox: fakeDisposableSandbox, // tests inject this; no Docker
|
|
53
|
+
});
|
|
54
|
+
composition.capabilities.networkIsolated; // copied, never invented
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Build:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
docker build -f packages/prism-work/sandbox/Dockerfile -t prism-work-sandbox packages/prism-work
|
|
61
|
+
docker image inspect --format '{{index .RepoDigests 0}}' prism-work-sandbox
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Extension and configuration notes
|
|
65
|
+
|
|
66
|
+
- Do not fork `createDockerSandbox`. Image pull/build stays outside Prism (`--pull=never`).
|
|
67
|
+
- Connectors optional and host-side. Bytes move via sandbox import/export and office filesystem tools.
|
|
68
|
+
- `work_exec` is argv-only (`file` + `args`); no model-supplied shell string.
|
|
69
|
+
- Protected image check: `PRISM_TEST_WORK_SANDBOX=1` runs `scripts/work-sandbox-image.test.mjs` and sandbox recalc/render/legacy-convert tests. Default `npm test` does not spawn `soffice`.
|
|
70
|
+
|
|
71
|
+
## Recalc and visual QA
|
|
72
|
+
|
|
73
|
+
In-process `SheetModel` does not evaluate formulas. Cached values are filled only by LibreOffice in this image (`network: none`, isolated `/tmp/lo-profile`, deleted with the container). Vendored scripts:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python3 /opt/prism-work/skills/skills/productivity/xlsx/scripts/xlsx_recalc.py /workspace/out.xlsx --timeout 60
|
|
77
|
+
python3 /opt/prism-work/skills/skills/productivity/powerpoint/scripts/pptx_render.py /workspace/deck.pptx --outdir /workspace/render
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Equivalent argv (wrapper already injects `-env:UserInstallation=file:///tmp/lo-profile --headless`):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
soffice --headless -env:UserInstallation=file:///tmp/lo-profile --convert-to pdf --outdir /tmp/out /workspace/out.xlsx
|
|
84
|
+
pdftoppm -png -r 100 /tmp/out/out.pdf /tmp/out/page
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
External workbook links cannot be fetched. Recalc then fails closed: formula stays, cached value missing or error — Prism does not invent a number. `soffice` timeout ≤ 60 s.
|
|
88
|
+
|
|
89
|
+
## Legacy convert
|
|
90
|
+
|
|
91
|
+
Prism AST still refuses non-ZIP packages. Convert OLE `.doc` / `.xls` / `.ppt` inside this image (`network: none`, isolated `/tmp/lo-profile`, macros refused), then `office_parse`:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
soffice --headless -env:UserInstallation=file:///tmp/lo-profile --convert-to docx --outdir /workspace /workspace/legacy.doc
|
|
95
|
+
soffice --headless -env:UserInstallation=file:///tmp/lo-profile --convert-to xlsx --outdir /workspace /workspace/legacy.xls
|
|
96
|
+
soffice --headless -env:UserInstallation=file:///tmp/lo-profile --convert-to pptx --outdir /workspace /workspace/legacy.ppt
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Do not enable macros. Encrypted OOXML stays dropped. No in-process OLE parser.
|
|
100
|
+
|
|
101
|
+
## Security and performance notes
|
|
102
|
+
|
|
103
|
+
- Default network none. Composition does not claim isolation the sandbox did not attest.
|
|
104
|
+
- Token env keys denied by name (`M365_*`, `GOOGLE_*`) at composition construct and `execFile`.
|
|
105
|
+
- LibreOffice wrapper: private `/tmp/lo-profile`, `--headless`, no macro enable, `--accept` refused. No listening socket. Legacy convert uses the same wrapper.
|
|
106
|
+
- Zip bombs: existing office parse caps; sandbox export uses existing export caps.
|
|
107
|
+
- Image build is CI/protected, not default unit tests. Composition construct is in-process with a fake sandbox.
|
|
108
|
+
|
|
109
|
+
## Related APIs
|
|
110
|
+
|
|
111
|
+
- [Coding security](coding-security.md) — `createDockerSandbox` digest pin, user, network none
|
|
112
|
+
- [Work tools](work-tools.md) — host-side connectors
|
|
113
|
+
- [Documents](documents.md) — `createOfficeTools`
|
|
114
|
+
- [Document reader](document-reader.md) — optional `reader` injection
|
|
115
|
+
- [Context and skills](context-and-skills.md) — `loadWorkSkills()` (`docx`, `xlsx`, `powerpoint`, `pdf`)
|
package/docs/work-tools.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Work tools
|
|
2
2
|
|
|
3
|
-
Optional `@arnilo/prism-
|
|
3
|
+
Optional `@arnilo/prism-work/connectors` subpath: identity-scoped Microsoft 365 and Google Workspace connectors. Host-pinned CLI binaries or pinned HTTP adapters; hard-coded operation maps; draft-then-approve mutations; side-effect idempotency; shared mail/calendar/file/task result shapes. HTTP file gets persist untrusted bytes to a host artifact store or contained filesystem, never the transcript.
|
|
4
4
|
|
|
5
5
|
## When to use
|
|
6
6
|
|
|
7
|
-
Use when agents must read or mutate tenant mail/calendar/files/tasks through
|
|
7
|
+
Use when agents must read or mutate tenant mail/calendar/files/tasks through a host-pinned enterprise CLI or a pinned HTTP adapter — not through model-built shell strings, model-supplied URLs, or generic Graph/Discovery free-form calls.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install @arnilo/prism-
|
|
12
|
+
npm install @arnilo/prism @arnilo/prism-work
|
|
13
13
|
# host separately:
|
|
14
14
|
# npm i -g @pnp/cli-microsoft365
|
|
15
15
|
# npm i -g @googleworkspace/cli
|
|
@@ -20,26 +20,28 @@ npm install @arnilo/prism-core/integrations/work
|
|
|
20
20
|
```ts
|
|
21
21
|
import {
|
|
22
22
|
createWorkTools,
|
|
23
|
-
|
|
23
|
+
createMicrosoft365HttpAdapter,
|
|
24
24
|
createGoogleWorkspaceCliAdapter,
|
|
25
|
+
createGoogleWorkspaceHttpAdapter,
|
|
25
26
|
createMemoryIdempotencyStore,
|
|
26
|
-
} from "@arnilo/prism-
|
|
27
|
-
|
|
27
|
+
} from "@arnilo/prism-work/connectors";
|
|
28
|
+
import { createOAuthWorkTokenProvider } from "@arnilo/prism-core/credentials/node";
|
|
29
|
+
// or: import { createGoogleWorkspaceCliAdapter } from "@arnilo/prism-work/connectors/google-workspace";
|
|
28
30
|
|
|
29
|
-
const microsoft365 =
|
|
30
|
-
binary: process.env.M365_BIN!,
|
|
31
|
-
configDir: `/var/prism/m365/${tenant}/${user}`,
|
|
31
|
+
const microsoft365 = createMicrosoft365HttpAdapter({
|
|
32
32
|
identity,
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
tokenProvider: createOAuthWorkTokenProvider({ provider: m365OAuth, store, envVar: "M365_ACCESSTOKEN" }),
|
|
34
|
+
accessEnvVar: "M365_ACCESSTOKEN",
|
|
35
35
|
});
|
|
36
|
+
// Or retain createMicrosoft365CliAdapter({ binary, configDir, identity }) for host-pinned m365.
|
|
36
37
|
|
|
37
|
-
const googleWorkspace =
|
|
38
|
-
binary: process.env.GWS_BIN!,
|
|
39
|
-
configDir: `/var/prism/gws/${tenant}/${user}`,
|
|
38
|
+
const googleWorkspace = createGoogleWorkspaceHttpAdapter({
|
|
40
39
|
identity,
|
|
40
|
+
tokenProvider: createOAuthWorkTokenProvider({ provider: gwsOAuth, store, envVar: "GOOGLE_ACCESS_TOKEN" }),
|
|
41
|
+
accessEnvVar: "GOOGLE_ACCESS_TOKEN",
|
|
41
42
|
// allowedOps: add docs.create / sheets.create / slides.create when gated
|
|
42
43
|
});
|
|
44
|
+
// Or retain createGoogleWorkspaceCliAdapter({ binary, configDir, identity }) for host-pinned gws.
|
|
43
45
|
|
|
44
46
|
const tools = createWorkTools({
|
|
45
47
|
microsoft365,
|
|
@@ -47,6 +49,9 @@ const tools = createWorkTools({
|
|
|
47
49
|
idempotencyStore: createMemoryIdempotencyStore(),
|
|
48
50
|
approval: { isApproved: ({ draftId }) => hostHasApproved(draftId) },
|
|
49
51
|
externalRecipients: { allow: (addr) => addr.endsWith("@contoso.com") },
|
|
52
|
+
scanAttachment: ({ bytes }) => hostScan(bytes), // required before file-get persistence
|
|
53
|
+
artifacts: hostWorkArtifacts, // creates ArtifactBodyRef values and owns body storage
|
|
54
|
+
filesystem: containedFilesystem, // optional destination/source for work-sandbox files
|
|
50
55
|
});
|
|
51
56
|
```
|
|
52
57
|
|
|
@@ -64,7 +69,9 @@ Verified against [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/
|
|
|
64
69
|
| `calendar.list` | `m365 outlook event list --output json` |
|
|
65
70
|
| `calendar.add` | `m365 outlook event add --output json --subject … --start … --end …` |
|
|
66
71
|
| `file.list` | `m365 file list --output json --webUrl … --folderUrl …` |
|
|
72
|
+
| `file.get` | HTTP only: `GET /me/drive/items/{id}/content` on `graph.microsoft.com` |
|
|
67
73
|
| `file.add` | `m365 file add --output json --folderUrl … --filePath …` |
|
|
74
|
+
| `file.copy` | `m365 file copy --output json --webUrl … --sourceUrl … --targetUrl …` (draft-then-approve) |
|
|
68
75
|
| `file.share` | `m365 spo file sharinglink add` (`--scope organization` only) |
|
|
69
76
|
| `todo.*` / `planner.*` | capability-gated via `allowedOps` |
|
|
70
77
|
|
|
@@ -80,16 +87,37 @@ Verified against [`@googleworkspace/cli` / `gws`](https://github.com/googleworks
|
|
|
80
87
|
| `calendar.list` | `gws calendar events list --params … --fields …` |
|
|
81
88
|
| `calendar.add` | `gws calendar events insert --params … --json …` |
|
|
82
89
|
| `file.list` | `gws drive files list --params … [--page-all]` (NDJSON when paginated) |
|
|
90
|
+
| `file.get` | HTTP only: `GET /drive/v3/files/{id}?alt=media` on `www.googleapis.com` |
|
|
83
91
|
| `file.add` | `gws drive files create --json … --upload …` |
|
|
84
92
|
| `file.share` | `gws drive permissions create` (`type=domain\|user` only; `anyone` denied) |
|
|
85
93
|
| `task.*` | `gws tasks tasks list\|insert\|patch` |
|
|
86
94
|
| `docs.create` / `sheets.create` / `slides.create` | capability-gated via `allowedOps` |
|
|
95
|
+
| `docs.update` / `sheets.update` / `slides.update` | capability-gated fixed-shape updates; never free-form batch requests |
|
|
87
96
|
|
|
88
|
-
|
|
97
|
+
### Microsoft 365 HTTP adapter
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
`createMicrosoft365HttpAdapter()` uses host-provided OAuth tokens only in the `Authorization` header and pinned fetch against `graph.microsoft.com`. Its fixed map covers Outlook messages/events, draft-then-approve OneDrive copy/sharing, and capability-gated To Do/Planner tasks. `ensureReady()` performs a bounded Graph `/me` request. `m365_file_get` accepts only an item ID and uses fixed `/me/drive/items/{id}/content`; it writes untrusted bytes to an artifact and/or contained filesystem after `scanAttachment`, returning only `{ artifact?, path?, byteLength, contentHash, untrusted: true }`. File list/upload/copy accepts an HTTPS Graph Drive-item URL; arbitrary SharePoint links are rejected rather than resolved with an extra request. The CLI adapter remains available for CLI-specific SharePoint paths.
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
### Google Workspace HTTP adapter
|
|
102
|
+
|
|
103
|
+
`createGoogleWorkspaceHttpAdapter()` uses host-provided OAuth tokens only in the `Authorization` header and pinned fetch against `docs.googleapis.com`, `gmail.googleapis.com`, `sheets.googleapis.com`, `slides.googleapis.com`, `www.googleapis.com`, and `tasks.googleapis.com`. Its operation map is fixed: Gmail messages, Calendar events, Drive files/permissions, Google Tasks, and capability-gated native Docs/Sheets/Slides creates plus draft-then-approve fixed-shape updates. Docs accepts only replace-text and insert-text requests; Sheets PUTs a string matrix with `valueInputOption=RAW`; Slides accepts only shape text insertion. No tool accepts a free-form `requests[]`. `ensureReady()` performs a bounded Gmail profile request; `gws_file_get` accepts only an item ID and uses fixed `Drive files.get?alt=media`, persisting untrusted bytes exactly like `m365_file_get`. `file.add` accepts a host-local path, `ArtifactBodyRef`, or contained sandbox path; its approved draft binds the content SHA-256 and rejects changed bytes. The CLI adapter remains available.
|
|
104
|
+
|
|
105
|
+
Startup: M365 CLI uses `version --output json`; M365 HTTP `ensureReady()` uses Graph `/me`; GWS CLI uses `--version`; GWS HTTP `ensureReady()` uses Gmail profile. Forbidden: `login`, `setup`, `auth`, `schema`, `doctor`, `--debug`, `--verbose`, credentials in argv, anonymous share, model-supplied command strings / URLs / free-form Discovery.
|
|
106
|
+
|
|
107
|
+
### Draft → approve → execute (0.7.0, R02)
|
|
108
|
+
|
|
109
|
+
Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and return `{ status: "pending_approval", draftId, revision, payloadDigest }` until the host approval gate grants permission.
|
|
110
|
+
|
|
111
|
+
In Prism 0.7.0, draft lifecycles are durably managed:
|
|
112
|
+
|
|
113
|
+
- **Exact revision binding**: Every draft carries an integer `revision` (starts at 1) and a deterministic canonical `payloadDigest` (`sha256:<hex>`). Approvals bind strictly to `{ draftId, revision, payloadDigest, identityKey, approvedAt, expiresAt, policyRevision }`.
|
|
114
|
+
- **Durable persistence across restarts**: When adapters are configured with `checkpoints: CheckpointStore` (e.g. `createPostgresEnterpriseState({ pool }).checkpoints` or `createMemoryCheckpointStore()`), drafts are stored under namespace `prism.work.draft`. Drafts survive process restarts; a worker process can resume an exact draft revision approved in a prior process or via a delayed human-in-the-loop review.
|
|
115
|
+
- **Edits invalidate approval**: Any mutation or update to a draft increments `revision`, recalculates `payloadDigest`, clears any previous `approval`, and resets `status` to `pending_approval`. Prior approvals cannot execute a modified draft.
|
|
116
|
+
- **Resuming approved drafts**: Mutation tools accept `{ draftId, revision }` without requiring callers to re-supply the full payload. The tool loads the stored draft, validates approval status and digest, reauthorizes immediately before execution, and executes the effect.
|
|
117
|
+
- **Idempotent duplicate approvals**: Re-approving an approved draft with the same approval object is idempotent. Submitting an approval with a mismatched revision or payload digest is rejected with `ERR_PRISM_WORK_DRAFT_STALE` or `ERR_PRISM_WORK_DRAFT_DIGEST`.
|
|
118
|
+
- **Ambiguous failure handling**: If a connector call fails ambiguously after dispatch, both the idempotency record and the draft are marked `unknown`. Re-running with that draft ID or idempotency key fails closed (`ERR_PRISM_WORK_IDEMPOTENCY_UNKNOWN`) and never auto-replays without explicit operator reconciliation.
|
|
119
|
+
- **File-byte binding**: `*_file_draft_upload` accepts a host-local path, `ArtifactBodyRef`, or contained sandbox path. Its payload digest includes `contentHash`; execution re-hashes bytes and rejects a changed source. Artifact reads verify their hash/size through `ArtifactBodyStore`.
|
|
120
|
+
- **Optional body offloading**: Supplying `bodies: ArtifactBodyStore` automatically stores large draft message/file bodies in the object store with an `ArtifactBodyRef` recorded on the draft metadata.
|
|
93
121
|
|
|
94
122
|
### Durable idempotency (0.0.23)
|
|
95
123
|
|
|
@@ -106,6 +134,8 @@ Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and
|
|
|
106
134
|
|
|
107
135
|
Call `begin({ identity, key, op })` **before** the external effect. After it succeeds, call `complete`, `fail`, or `markUnknown` with the returned claim token and version. The connector effect stays outside the database transaction, so this is claim-before-effect/deduplication—not exactly-once delivery. Claims default to 15 minutes (hard 60 minutes); expired claims transition to `unknown`; attempts default to 3 (hard 5). Stored rows contain no request body, token, raw provider response, or unrestricted payload.
|
|
108
136
|
|
|
137
|
+
Durable adapters reject with the portable codes `ERR_PRISM_WORK_IDEMPOTENCY` (a claim or payload the adapter refuses) and `ERR_PRISM_WORK_IDEMPOTENCY_CONFLICT` (a lost race, a stale claim token, or a transition out of order). Match on `error.code`: the error *class* is adapter-specific — the in-memory store raises `WorkToolError`, `createPostgresEnterpriseState(...).workIdempotency` raises `EnterprisePostgresError`, because `@arnilo/prism-core` cannot depend on `@arnilo/prism-work` at runtime — so an `instanceof` check that worked against the pre-move import path will silently stop matching. `packages/prism-core/src/enterprise/postgres/__tests__/work-idempotency.integration.test.ts` drives both adapters through the same conflict scenarios and asserts they report the same codes.
|
|
138
|
+
|
|
109
139
|
## Subprocess environment isolation (0.2.0, plan 020 Task 3)
|
|
110
140
|
|
|
111
141
|
`createCliRunner` never inherits the host environment. The child process receives only:
|
|
@@ -135,6 +165,7 @@ Environment maps are validated before spawn: NUL-free, `[A-Za-z_][A-Za-z0-9_]*`
|
|
|
135
165
|
| Pagination pages | 20 / 100 |
|
|
136
166
|
| Items / aggregate | 50/500 ; 200/2000 |
|
|
137
167
|
| Body / stdout | 256 KiB–2 MiB / 2–16 MiB |
|
|
168
|
+
| Download / upload file | 10 MiB / 50 MiB |
|
|
138
169
|
| Process wall time | 60 s / 10 min |
|
|
139
170
|
| Concurrent CLI / identity | 2 / 8 |
|
|
140
171
|
|
|
@@ -145,9 +176,10 @@ Approved mutations require core-derived `context.idempotencyKey` and a configure
|
|
|
145
176
|
## Security
|
|
146
177
|
|
|
147
178
|
- Require host-verified `AgentIdentity`; no cross-identity configDir reuse.
|
|
148
|
-
- Connector tokens
|
|
179
|
+
- Connector tokens: a `tokenProvider` resolves a per-identity access token only at the connector edge — into CLI env for CLI adapters or an `Authorization` header for HTTP adapters, never argv or model context. A missing/expired/revoked/cross-identity/wrong-tenant token fails the call closed before dispatch. Refresh is late-bound and single-flighted per account. Build one with `createOAuthWorkTokenProvider()` from `@arnilo/prism-core/credentials/node`.
|
|
149
180
|
- External mail recipients fail closed unless `externalRecipients.allow` returns true.
|
|
150
181
|
- Anonymous / `anyone` sharing denied.
|
|
182
|
+
- File gets accept IDs, never URLs; their response stream is cancelled at `maxFileBytes`, scanned before persistence, and returned only as artifact/path metadata with `untrusted: true`.
|
|
151
183
|
- CLI stdout/stderr capped (linear chunk capture, killed/rejected before bytes beyond the cap are retained); NDJSON page streams strictly parsed and page-capped; process killed on timeout/abort/overflow.
|
|
152
184
|
- Subprocess environment isolated (0.2.0): fixed allow-listed base + explicit `env` + late-bound token env; `HOME`/telemetry controls forced; reserved/duplicate/NUL/over-cap env and non-absolute binary/configDir fail before spawn. See [Subprocess environment isolation](#subprocess-environment-isolation-020-plan-020-task-3).
|
|
153
185
|
|
package/docs/workflows.md
CHANGED
|
@@ -20,6 +20,9 @@ Primary exports:
|
|
|
20
20
|
| `defineSaga` / `runSaga` / `resumeSaga` | Bounded linear durable forward steps, reverse compensation, unknown-outcome reconciliation, lease fencing, and manual resolution over existing checkpoint/lease stores |
|
|
21
21
|
| `createWorkflowSchedules` | Explicit ownership-scoped one-time/interval/host-calculated schedules over existing checkpoint/lease stores |
|
|
22
22
|
| `createProactiveScheduleCapabilities` | Scoped, expiring, revocable capability tokens that enable proactive schedules; revocation stops firing fail-closed |
|
|
23
|
+
| `serializeWorkflowGraph` / `collectWorkflowGraphs` | Pure JSON serialization of workflow DAGs (`WorkflowGraphView`) without functions/closures; collect nested graphs |
|
|
24
|
+
| `workflowGraphToMermaid` / `workflowGraphToDot` | Deterministic Mermaid flowchart and Graphviz DOT exporters with node shapes by kind and label escaping |
|
|
25
|
+
| `projectWorkflowGraphRun` / `createWorkflowGraphRunFolder` | Run overlay view (`WorkflowGraphRunView`) from checkpoints, timelines, or live event stream |
|
|
23
26
|
|
|
24
27
|
Included through the `@arnilo/prism` / `@arnilo/prism-core` family packages; installing them does not start workflows. Interactive TUI is out of scope (C-012 deferred).
|
|
25
28
|
|
|
@@ -106,7 +109,7 @@ Every node receives bounded `ctx.state`, `ctx.stateVersion`, and async `ctx.upda
|
|
|
106
109
|
|
|
107
110
|
`replayWorkflow(workflow, { sourceRunId, fromNodeId, runId? }, options)` requires a succeeded source/node, creates a new checkpoint, copies terminal evidence outside the selected node's downstream closure, restores selected-node pre-state, and records `{ sourceRunId, fromNodeId, rootRunId, depth }`. Source evidence is untouched. Copying any prior nested/tool approval is rejected; replay from that approval node or earlier so Phase 8 approval executes again.
|
|
108
111
|
|
|
109
|
-
`createWorkflowCoordinator({ coordinatorId, workflows, checkpoints, leases, ... })` polls queued/running checkpoints with bounded pages, atomically claims each run, renews its lease, and aborts/fences work after lease loss. Key controls: `leaseTtlMs` (default 30s), `renewalIntervalMs` (default TTL/3), `pollIntervalMs` (default 1s), `maxConcurrentRuns` (default 4), and `pageSize` (default 100, maximum 500).
|
|
112
|
+
`createWorkflowCoordinator({ coordinatorId, workflows, checkpoints, leases, ... })` polls queued/running checkpoints with bounded pages, atomically claims each run, renews its lease, and aborts/fences work after lease loss. Key controls: `leaseTtlMs` (default 30s), `renewalIntervalMs` (default TTL/3), `pollIntervalMs` (default 1s), `maxConcurrentRuns` (default 4), and `pageSize` (default 100, maximum 500). Optional `admission` wraps claims: cursor wrap across pages (default 4 pages/poll, hard 16) so a noisy first page cannot starve later tenants; `perTenant` / `perClass` cap concurrent claims on that worker; `deadlineMs` skips stale `createdAt`; `drain` stops new claims while draining and aborts in-flight after `snapshot().expired`. Workload class is `metadata.workloadClass` (`^[a-z][a-z0-9_-]{0,31}$`, else `default`). `onMetric` labels are `outcome` + `class` only — never tenant or run ids. This is not a second scheduler.
|
|
110
113
|
|
|
111
114
|
`defineSaga({ id, revision, steps })` validates a bounded linear definition. Each step supplies `run`, `compensate`, and `reconcile`; handlers receive a stable tenant-scoped `operationId`, redacted bounded input/output, prior outputs, and an abort signal. `runSaga(definition, { checkpoints, leases, ownerId, tenantId, runId?, input?, maxAttempts?, leaseTtlMs?, redactor?, onEvent? })` stores a surrogate workflow checkpoint through `WorkflowCheckpointAdapter`, acquires a fenced `LeaseStore` lease, and advances one cursor at a time. `resumeSaga` takes over an expired run; it never replays durably succeeded steps. Forward or compensation handlers mark ambiguous failures with `unknown: true` (or `ERR_PRISM_SAGA_UNKNOWN`), and `reconcile` must return `succeeded`, `failed`, or `unknown` before retry.
|
|
112
115
|
|
|
@@ -404,6 +407,70 @@ For a single bounded refinement, prefer `loopNode`. Keep this host-loop pattern
|
|
|
404
407
|
- Agent exclusivity is per session: one active `run()` at a time, same as core.
|
|
405
408
|
- Saga definitions remain host code and only their revision, ordered step IDs, bounded JSON snapshots, cursors, attempt counters, and redacted error/provenance metadata are persisted. The surrogate workflow checkpoint namespace is private to the package.
|
|
406
409
|
|
|
410
|
+
## Graph serialization, Mermaid, DOT, and run overlay
|
|
411
|
+
|
|
412
|
+
Workflows can be converted to JSON view-models and visualization formats without executing them or shipping JS functions:
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import {
|
|
416
|
+
collectWorkflowGraphs,
|
|
417
|
+
createWorkflowGraphRunFolder,
|
|
418
|
+
projectWorkflowGraphRun,
|
|
419
|
+
serializeWorkflowGraph,
|
|
420
|
+
workflowGraphToDot,
|
|
421
|
+
workflowGraphToMermaid,
|
|
422
|
+
} from "@arnilo/prism-core/runtime/workflows";
|
|
423
|
+
|
|
424
|
+
// 1. Pure static graph view (JSON-serializable)
|
|
425
|
+
const view = serializeWorkflowGraph(workflow);
|
|
426
|
+
// view.nodes: [{ id: "stepA", kind: "function", label: "stepA" }, ...]
|
|
427
|
+
// view.edges: [{ from: "stepA", to: "stepB", kind: "always" }, ...]
|
|
428
|
+
|
|
429
|
+
// 2. Export to Mermaid flowchart
|
|
430
|
+
const mermaid = workflowGraphToMermaid(view);
|
|
431
|
+
// flowchart TD
|
|
432
|
+
// stepA["stepA"]
|
|
433
|
+
// stepB["stepB"]
|
|
434
|
+
// stepA --> stepB
|
|
435
|
+
|
|
436
|
+
// 3. Export to Graphviz DOT
|
|
437
|
+
const dot = workflowGraphToDot(view);
|
|
438
|
+
|
|
439
|
+
// 4. Project run state overlay from checkpoint or timeline
|
|
440
|
+
const overlay = projectWorkflowGraphRun(view, checkpoint);
|
|
441
|
+
// overlay.nodes[0].run?.status -> "succeeded" | "failed" | "running" | ...
|
|
442
|
+
|
|
443
|
+
// 5. Incremental live folder for WebSocket/SSE cockpits
|
|
444
|
+
const folder = createWorkflowGraphRunFolder(view);
|
|
445
|
+
for await (const event of eventBus.subscribe()) {
|
|
446
|
+
folder.push(event);
|
|
447
|
+
const live = folder.snapshot();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 6. Collect nested workflow graphs by ID
|
|
451
|
+
const graphs = collectWorkflowGraphs(hierarchicalWorkflow);
|
|
452
|
+
// Map with parent and child workflow views
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Graph view-model (`WorkflowGraphView`)
|
|
456
|
+
|
|
457
|
+
- `schemaVersion`: 1
|
|
458
|
+
- `workflowId`, `revision`, `definitionHash`: matches definition
|
|
459
|
+
- `nodes`: deterministically sorted array of `WorkflowGraphNode` with `id`, `kind`, `label`, `metadata`, `nestedWorkflowId`, `loop`
|
|
460
|
+
- `edges`: deterministically sorted array of `WorkflowGraphEdge` with `from`, `to`, `kind` (`"always" | "then" | "else"`)
|
|
461
|
+
- Closures and functions (`when`, `execute`, `map`, `reduce`) are **omitted** by design so the view is safe for JSON wire transfer and audit logging.
|
|
462
|
+
|
|
463
|
+
### Visualization & escaping
|
|
464
|
+
|
|
465
|
+
- `workflowGraphToMermaid()` assigns distinct shapes by node kind: diamond for `conditional`, hexagon for `loop`, stadium for `agent`, subroutine for `workflow`, rounded for `tool`, box for `function`.
|
|
466
|
+
- Mermaid and DOT exporters automatically escape double quotes, HTML characters (`<`, `>`), and literal arrows (`-->`) to prevent script injection (XSS) and parser corruption.
|
|
467
|
+
- Output is byte-identical across runs regardless of dictionary key insertion order.
|
|
468
|
+
|
|
469
|
+
### Run overlay (`WorkflowGraphRunView`)
|
|
470
|
+
|
|
471
|
+
- Paints per-node runtime status (`status`, `durationMs`, `attempt`, `errorCode`, `skippedReason`) onto the static DAG structure.
|
|
472
|
+
- Does **not** include node outputs — outputs remain on the execution timeline under content-capture policy to protect credentials and manage payload size.
|
|
473
|
+
|
|
407
474
|
## Security and performance notes
|
|
408
475
|
|
|
409
476
|
- Definitions require a non-empty host-authored `revision` and fail closed on cycles, unknown edges, self-edges, invalid limits, and `maxNodes` overflow. Revision and every nested revision enter the deterministic definition hash; hosts must bump revision when function/tool behavior changes. Loop `maxIterations` is required and capped at 64.
|
|
@@ -440,6 +507,7 @@ Use workflows for known, durable, replayable graphs. Use optional supervisor del
|
|
|
440
507
|
- [A2A interoperability](a2a.md): hosts may adapt existing exact-owner workflow status/list/cancel/checkpoint/event surfaces to `A2ATaskLifecycle`; A2A package adds no workflow worker, queue, or schema.
|
|
441
508
|
- [Agent events](agent-events.md): core `AgentEvent` wrapped by `agent_event`
|
|
442
509
|
- [Session stores and branching](session-stores-and-branching.md): session `leafId` reuse on resume
|
|
510
|
+
- [Observational memory compaction](compaction-observational-memory.md): hosts may pass `ctx.nodeId` to `withWorkScope`; the workflow runner stays scope-unaware.
|
|
443
511
|
- [CLI/RPC](cli-rpc.md): host control seam; wire `createWorkflowCommands()` into `runRpcServer`
|
|
444
512
|
- [Database persistence](database-persistence.md): generic `CheckpointStore` and `LeaseStore` capabilities
|
|
445
513
|
- [SQLite persistence](sqlite-persistence.md): durable `persistence.checkpoints`
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
`@arnilo/prism-memory` is an optional package for schema/template-backed working memory and embedding-based semantic recall. It owns narrow `Embedder` and `VectorStore` contracts reused by the `@arnilo/prism-memory/rag` subpath, plus an in-memory reference path and one PostgreSQL/pgvector production adapter.
|
|
6
6
|
|
|
7
|
+
The [memory fabric](memory-fabric.md) subpath is a typed-notes layer on top of these same stores: notes are ordinary rows here, so this page's consent, redaction, lineage, and scope rules are the whole rulebook. [Observational memory](compaction-observational-memory.md) is a separate, **episodic** layer — a source-backed ledger for the current session — and is not a store this package owns or replaces.
|
|
8
|
+
|
|
7
9
|
## When to use it
|
|
8
10
|
|
|
9
11
|
Use it when a host needs durable per-tenant profile/state (working memory) or top-K semantic retrieval over prior thread entries. Do not use it as a replacement for observational memory compaction: observational memory compresses source-backed observations; semantic memory retrieves embeddings; working memory stores the current structured profile.
|
|
@@ -27,6 +29,7 @@ Ordinary Prism sessions do not require this package or any vector backend.
|
|
|
27
29
|
| `redactor` / `secrets` | no | Redact text/metadata before persist/inject |
|
|
28
30
|
| `requireConsent` | no | Strict mode: recall/injection excludes entries lacking explicit consent |
|
|
29
31
|
| `importanceFrom` | no | Host-owned hook deriving importance from a redacted reflection payload (write time only; no default, no LLM) |
|
|
32
|
+
| `onInvalidate` | no | After lineage rows land, before body delete (observational drop / RAG delete wiring) |
|
|
30
33
|
|
|
31
34
|
Semantic indexing (entries carry `MemoryConsent` source/visibility; unset defaults to `{ source: "user", scope: "thread", visible: true }`):
|
|
32
35
|
|
|
@@ -38,13 +41,13 @@ Semantic indexing (entries carry `MemoryConsent` source/visibility; unset defaul
|
|
|
38
41
|
| `grantedAt` / `revokedAt` | Optional host/audit timestamps; a revocation excludes the record. |
|
|
39
42
|
|
|
40
43
|
```ts
|
|
41
|
-
await memory.remember({ entries: [{ id, text, metadata?, consent?, sequence?, importance?, reflection? }] }, { wait?: boolean })
|
|
44
|
+
await memory.remember({ entries: [{ id, text, metadata?, consent?, sequence?, importance?, reflection?, lineage?: { sourceIds, reason? } }] }, { wait?: boolean })
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
Semantic recall (honors consent/visibility at assembly time):
|
|
45
48
|
|
|
46
49
|
```ts
|
|
47
|
-
await memory.recall(query, { topK?, messageRange?, requireConsent?, scoring?, signal? })
|
|
50
|
+
await memory.recall(query, { topK?, messageRange?, requireConsent?, scoring?, explain?, shareFromParentThreadId?, signal? })
|
|
48
51
|
```
|
|
49
52
|
|
|
50
53
|
#### Composite recall scoring (opt-in)
|
|
@@ -99,8 +102,10 @@ Consent + lifecycle (real grant/correct/delete/retention on stored entries):
|
|
|
99
102
|
```ts
|
|
100
103
|
await memory.setConsent(entryId, { visible?: boolean, source?, scope? }) // grant/revoke; no re-embed
|
|
101
104
|
await memory.correct(entryId, text) // re-embeds, preserves consent
|
|
102
|
-
await memory.forget({ ids? })
|
|
103
|
-
await memory.
|
|
105
|
+
await memory.forget({ ids?, hold? }) // real delete; hold:true = legal_hold, no body delete
|
|
106
|
+
await memory.shareWith(childThreadId, sourceIds, { expiresAt? }) // parent→child allow-list; empty ids revoke
|
|
107
|
+
await memory.revokeShare(childThreadId)
|
|
108
|
+
await memory.applyRetention({ maxAgeDays?, maxEntries?, batchSize? }) // bounded real-delete sweep; skips legal_hold
|
|
104
109
|
|
|
105
110
|
const page = await memory.exportMemory({
|
|
106
111
|
identity: { tenantId, resourceId, threadId }, // exact host-verified owner
|
|
@@ -117,9 +122,10 @@ const rebuilt = await memory.rebuildIndex({ cursor?, batchSize?, maxMs?, signal?
|
|
|
117
122
|
| --- | --- |
|
|
118
123
|
| `updateWorking` / `getWorking` | Versioned `WorkingMemoryRecord` |
|
|
119
124
|
| `remember` | `{ accepted, pending, done }` — default `wait: false` indexes asynchronously |
|
|
120
|
-
| `recall` | `{ hits, adjacent }` tenant/thread scoped; invisible/revoked entries excluded |
|
|
121
|
-
| `setConsent` / `correct` | Updated `MemoryVectorRecord
|
|
122
|
-
| `forget` | Removed count (real delete) |
|
|
125
|
+
| `recall` | `{ hits, adjacent, explanations? }` tenant/thread scoped; invisible/revoked/invalidated entries excluded |
|
|
126
|
+
| `setConsent` / `correct` | Updated `MemoryVectorRecord`; revoke/correct marks lineage before dependents can inject |
|
|
127
|
+
| `forget` | Removed count (real delete); `0` when `hold: true` |
|
|
128
|
+
| `shareWith` / `revokeShare` | Parent-child grant; sibling threads cannot use it |
|
|
123
129
|
| `applyRetention` | `{ deleted, scanned }` bounded real-delete sweep |
|
|
124
130
|
| `exportMemory` | `{ entries, bytes, nextCursor? }` redacted, explicitly consented, identity-bound page |
|
|
125
131
|
| `rebuildIndex` | `{ rebuilt, nextCursor? }` re-embedded bounded page; caller owns resume scheduling |
|
|
@@ -215,8 +221,10 @@ const store = await createPostgresVectorStore({
|
|
|
215
221
|
dimension: 32, // optional; pins the embedding column width (HNSW + drift guard)
|
|
216
222
|
}); // PostgresVectorStoreOptions; dimension must match the embedder's dimensions
|
|
217
223
|
// store implements rag's VectorStore/TransactionalVectorStore contract: upsert,
|
|
218
|
-
// query, getBySource, transaction, lexicalQuery (fts, when available),
|
|
219
|
-
// getCurrentGeneration/setCurrentGeneration
|
|
224
|
+
// query, getBySource, transaction, lexicalQuery (fts, when available),
|
|
225
|
+
// getCurrentGeneration/setCurrentGeneration, and document ACL
|
|
226
|
+
// (`authorization: "acl"`, setSourceAccess, checkSourceAccess).
|
|
227
|
+
// close() ends adapter-owned pools.
|
|
220
228
|
```
|
|
221
229
|
|
|
222
230
|
`createPostgresVectorStore()` is the production counterpart to `createMemoryVectorStore()` used by the `rag` subpath; `createPostgresMemoryStores()` reuses the same vector implementation internally.
|
|
@@ -226,9 +234,9 @@ const store = await createPostgresVectorStore({
|
|
|
226
234
|
- Hosts wire the context provider into `AgentConfig.context` or `resolveContextProviders()`.
|
|
227
235
|
- The working-memory processor is opt-in and host-invoked; middleware is not required.
|
|
228
236
|
- `createHashEmbedder()` is for tests/demos only; production hosts supply a real `Embedder`.
|
|
229
|
-
- Observational memory (`/compaction/observational-memory`)
|
|
230
|
-
- Consent is enforced at the single `recall()` gate, so both direct recall and `createContextProvider()` injection honor it; `visible: false` (or a revoked grant) keeps an entry out of prompts, events, exports, and telemetry. `setConsent`/`correct` re-upsert in place (consent change does not re-embed)
|
|
231
|
-
- The PostgreSQL vector path owns its DDL in Prism (`buildMemoryDdl`/`buildVectorSearchDdl` exported): the `<table>_rag_scope_generations` per-scope generation pointer table, `text_tsv` tsvector column + GIN index for the lexical RAG leg, and an HNSW index when the embedding dimension is pinned. DDL runs against the host's **knowledge database** — the host names `schema`/`table` (defaults `prism_memory`/`semantic_memory`), owns backup/retention of that database, and can run migrations manually with `skipMigrations: true`. Identifiers are validated/quoted; values stay parameterized.
|
|
237
|
+
- Observational memory (`/compaction/observational-memory`) is composable. Stamp `lineage.sourceIds` on semantic writes; pass the same ids as `invalidatedIds` into observational projection/recall, or append `om.observations.dropped` from `onInvalidate`. Multi-source facts stay injectable only if none of their sources are invalidated (regenerate from remaining evidence).
|
|
238
|
+
- Consent is enforced at the single `recall()` gate, so both direct recall and `createContextProvider()` injection honor it; `visible: false` (or a revoked grant) keeps an entry out of prompts, events, exports, and telemetry. `setConsent`/`correct` re-upsert in place (consent change does not re-embed) and write invalidation rows first. `forget`/`applyRetention` are real deletes after those rows land; `forget({ hold: true })` keeps the body for legal hold but still excludes injection/export. A revoked grant is not a legal hold: it blocks injection/export but `forget` still purges the body. Legacy records without `_lineage` are self-only: only their own id is excluded. Caps: walk depth 8, 256 edges, 32 source ids, 64-row delete batches — over-cap throws rather than leak. No claim to retract prior disclosures.
|
|
239
|
+
- The PostgreSQL vector path owns its DDL in Prism (`buildMemoryDdl`/`buildVectorSearchDdl` exported): the `<table>_rag_scope_generations` per-scope generation pointer table, `<table>_rag_source_acl` principal/group grants (query-time EXISTS, indexed by principal and group), `<table>_invalidation` tombstones (query-time NOT EXISTS; `corrected` keeps the source), `<table>_share_grant` parent-child allow-lists, GIN on `metadata._lineage.sourceIds`, `text_tsv` tsvector column + GIN index for the lexical RAG leg, and an HNSW index when the embedding dimension is pinned. DDL runs against the host's **knowledge database** — the host names `schema`/`table` (defaults `prism_memory`/`semantic_memory`), owns backup/retention of that database, and can run migrations manually with `skipMigrations: true`. Identifiers are validated/quoted; values stay parameterized.
|
|
232
240
|
- `createPostgresVectorStore({ dimension })` pins the embedding column width before building indexes: pgvector can only build HNSW over `vector(N)` columns, and dimension mismatch fails closed instead of drifting.
|
|
233
241
|
- `exportMemory()` requires an exact `{ tenantId, resourceId, threadId }` identity equal to its `createMemory()` scope. It excludes legacy consent-less, invisible, and revoked records even when normal recall allows legacy entries. It returns a stable sequence cursor page, redacted before response, with defaults/hard caps of 100/200 entries, 4/32 MiB, and 10/60 seconds. `rebuildIndex()` uses the same stable cursor shape to re-embed one 32/128-record page under a 10/60-second cap; save the cursor durably to resume. Both APIs require a store implementing bounded `listByThread()`; retention also requires `countByThread()`. PostgreSQL/pgvector and the in-memory reference adapter conform; SQLite persistence stores sessions, not semantic vectors.
|
|
234
242
|
- Profile bundles do not include this package yet.
|
|
@@ -249,7 +257,9 @@ await runMemoryConformance(() => ({
|
|
|
249
257
|
|
|
250
258
|
- Every write/query/delete requires `tenantId` + `resourceId`; semantic paths also require `threadId`.
|
|
251
259
|
- Cross-tenant and cross-thread access is denied.
|
|
252
|
-
-
|
|
260
|
+
- RAG document ACL (`RagAccessConstraint`) is host-verified and applied inside `query`/`lexicalQuery` before ranking when `authorization` is passed. Missing grants and unresolved access versions deny. `filter` is not ACL.
|
|
261
|
+
- Revoked/invisible/non-consented/invalidated memories never enter prompts, events, exports, or telemetry; `requireConsent: true` additionally drops consent-less (legacy) entries. Query-time invalidation is an indexed NOT EXISTS (no full-corpus scan on recall). Parent-child shares are explicit, tenant-bound, expiring, and fail closed when missing. Cross-tenant lineage/grants reject.
|
|
262
|
+
- `revokedIdsAbsent(environment, deniedIds)` is the 072 invariant body (`metadata.invariant: true`, score 0 cannot be averaged away). Hosts wrap it with `defineScorer`.
|
|
253
263
|
- Configure `secrets` / `redactor` so memory text and metadata cannot persist or inject raw canaries.
|
|
254
264
|
- Injected context is inert text — it cannot grant tools or permissions.
|
|
255
265
|
- Hard caps: top-K ≤ 32, messageRange ≤ 4, embed batch ≤ 128, injected tokens ≤ 8000, payload/working-memory byte limits enforced.
|
|
@@ -266,6 +276,7 @@ Supervisor child factories receive unique derived `resourceId` and `threadId` va
|
|
|
266
276
|
- [Supervisor delegation](supervisors.md): package-derived child resource/thread scope.
|
|
267
277
|
- [Retrieval-augmented generation](rag.md): bounded document chunks reuse this package's embed/vector contracts.
|
|
268
278
|
- [Context and skills](context-and-skills.md): `ContextProvider` injection seam.
|
|
269
|
-
- [Observational memory compaction package](compaction-observational-memory.md): source-backed observation/reflection memory distinction.
|
|
279
|
+
- [Observational memory compaction package](compaction-observational-memory.md): source-backed observation/reflection memory distinction; still episodic, owned by that subpath, not by these stores.
|
|
270
280
|
- [PostgreSQL persistence](postgres-persistence.md): session/run persistence; memory vectors live in this optional package instead.
|
|
271
281
|
- [Middleware hooks](middleware-hooks.md): reuse existing `context` hook if hosts transform injected blocks.
|
|
282
|
+
- [Memory fabric](memory-fabric.md): typed notes over these stores, with recall explanations and conversation search.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnilo/prism",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Agent harness for AI providers, agents, sessions, and tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -125,9 +125,10 @@
|
|
|
125
125
|
"packages/mcp",
|
|
126
126
|
"packages/prism-providers",
|
|
127
127
|
"packages/memory",
|
|
128
|
+
"packages/prism-work",
|
|
128
129
|
"packages/prism-core",
|
|
130
|
+
"packages/prism-channels",
|
|
129
131
|
"packages/prism-coding-tools",
|
|
130
|
-
"packages/office",
|
|
131
132
|
"packages/ag-ui",
|
|
132
133
|
"packages/web-tools",
|
|
133
134
|
"packages/acp-agent"
|
|
@@ -146,7 +147,8 @@
|
|
|
146
147
|
"format": "biome format --write .",
|
|
147
148
|
"format:check": "biome format .",
|
|
148
149
|
"pack:dry-run": "npm pack --dry-run && npm run pack:dry-run --workspaces --if-present",
|
|
149
|
-
"test:postgres": "node scripts/
|
|
150
|
+
"test:postgres": "node scripts/postgres-evidence.mjs",
|
|
151
|
+
"test:postgres:run": "node scripts/require-postgres-url.mjs && npm run test:postgres --workspace @arnilo/prism-core --if-present && npm run test:postgres --workspace @arnilo/prism-memory && npm run test:postgres --workspace @arnilo/prism-channels && node --test scripts/phase7-conformance.test.mjs scripts/phase12-restart-recovery.test.mjs scripts/phase22-conformance.test.mjs",
|
|
150
152
|
"test:nats": "node scripts/require-nats-url.mjs && npm run test:nats --workspace @arnilo/prism-core --if-present",
|
|
151
153
|
"release:dry-run": "npm run sdk:ready",
|
|
152
154
|
"release:check": "node scripts/release.mjs check",
|
package/templates/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Ready-to-run project templates for `prism init --template <name>`.
|
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| `init` | Minimal starter Prism agent with one selected provider and offline mock test | `@arnilo/prism` |
|
|
10
10
|
| `deep-research` | Flagship deep research agent: plan -> search -> extract -> refine loop -> citations -> HITL clarify | `@arnilo/prism`, `@arnilo/prism-web-tools`, `@arnilo/prism-memory`, `@arnilo/prism-workflows` |
|
|
11
|
+
| `personal-assistant` | Personal workstation assistant: single-user ownership, local tools, credential references, and dev inspection | `@arnilo/prism`, `@arnilo/prism-core`, `@arnilo/prism-providers` |
|
|
12
|
+
| `business-worker` | Multi-tenant business worker: durable storage, tenant isolation, verified identity, and governed boundaries | `@arnilo/prism`, `@arnilo/prism-core`, `@arnilo/prism-providers` |
|
|
11
13
|
|
|
12
14
|
## Usage
|
|
13
15
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
Multi-tenant business worker built with Prism. Features strict tenant isolation, verified identities, durable persistence, and governed invocation boundaries.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm test
|
|
10
|
+
npm start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Production Readiness Requirements
|
|
14
|
+
|
|
15
|
+
- **Tenant Isolation**: Non-empty `tenantId` in ownership; tenant-scoped stores and workspaces.
|
|
16
|
+
- **Verified Identity**: Host-verified principal matching tenant ownership; no fabricated identities.
|
|
17
|
+
- **Durable Persistence**: Production readiness strictly rejects in-memory stores; PostgreSQL or SQLite durable stores required.
|
|
18
|
+
- **Workspace Containment**: Execution confined to authorized tenant sandbox directories.
|
|
19
|
+
- **Governed Invocation**: Permission policies, trust verification, parameter validation, and secret redaction enforced.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
BUSINESS_API_KEY=
|