esoul-sdk 0.21.0 → 0.21.1
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
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Releases before 0.20.0 are recorded in the repository history only.
|
|
4
4
|
|
|
5
|
+
## 0.21.1
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **SimGmail people answer an agent's mail.** A persona that is a person (`reply`, `optout`) now reads and
|
|
10
|
+
answers mail marked `Auto-Submitted` (what an app's autonomous agent sends, RFC 3834) — as people do. Only a
|
|
11
|
+
machine responder (`ooo`) stays silent to it, and a dead address bounces it. Before, every persona ignored
|
|
12
|
+
it, so a simulated conversation with an autonomous agent stopped after the first reply.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`esoul-sdk/testing` exports the failed-request list** (`listFailedRequests`, `subscribeFailedRequests`,
|
|
17
|
+
`resetFailedRequests`) so a test can assert that a click which failed reverted and was named on the banner
|
|
18
|
+
(`runOptimistic` from `esoul-sdk/react`).
|
|
19
|
+
|
|
5
20
|
## 0.21.0
|
|
6
21
|
|
|
7
22
|
### Added
|
package/api-reference.md
CHANGED
|
@@ -3928,11 +3928,11 @@ interface WorkspaceTools {
|
|
|
3928
3928
|
```
|
|
3929
3929
|
|
|
3930
3930
|
==============================================================================
|
|
3931
|
-
## `esoul-sdk/testing` —
|
|
3931
|
+
## `esoul-sdk/testing` — 56 exports
|
|
3932
3932
|
|
|
3933
3933
|
Tests: run an op as a persona, an in-memory database with the real rules, recorders.
|
|
3934
3934
|
|
|
3935
|
-
### Functions and values (
|
|
3935
|
+
### Functions and values (16)
|
|
3936
3936
|
|
|
3937
3937
|
#### `capture` — function · src/testing/ops.ts
|
|
3938
3938
|
|
|
@@ -3982,6 +3982,12 @@ A caller to run something as. Give it a `role` when your app declares its own vo
|
|
|
3982
3982
|
function fakeViewer( kind: ViewerKind, opts: { userId?: string | null; viewerIds?: string[]; role?: string; name?: string | null; email?: string | null; attrs?: Record<string, string | number | boolean> } = {}, ): RuleViewer
|
|
3983
3983
|
```
|
|
3984
3984
|
|
|
3985
|
+
#### `listFailedRequests` — function · src/failed-requests.ts
|
|
3986
|
+
|
|
3987
|
+
```ts
|
|
3988
|
+
function listFailedRequests(): FailedRequest[]
|
|
3989
|
+
```
|
|
3990
|
+
|
|
3985
3991
|
#### `memoryDb` — function · src/testing/db.ts
|
|
3986
3992
|
|
|
3987
3993
|
A database for your app, from your manifest. Starts as the app's own code (an `internal` caller) so a test can seed rows, then `.as(someone)` to check what each kind of person may actually see.
|
|
@@ -3998,6 +4004,14 @@ An in-memory `FilesApi` over a tree you write in the test: folders are objects,
|
|
|
3998
4004
|
function memoryFiles(sources: Record<string, MemoryFileTree>, opts?: { readOnly?: string[] }): MemoryFiles
|
|
3999
4005
|
```
|
|
4000
4006
|
|
|
4007
|
+
#### `resetFailedRequests` — function · src/failed-requests.ts
|
|
4008
|
+
|
|
4009
|
+
Test-only: start from nothing.
|
|
4010
|
+
|
|
4011
|
+
```ts
|
|
4012
|
+
function __resetFailedRequests(): void
|
|
4013
|
+
```
|
|
4014
|
+
|
|
4001
4015
|
#### `roleForKind` — function · src/testing/db.ts
|
|
4002
4016
|
|
|
4003
4017
|
The app's own word for a kind of caller, read from the manifest's `roles`.
|
|
@@ -4038,6 +4052,12 @@ A real (tiny) OAuth2 provider for exercising the plugin-connection flow: /author
|
|
|
4038
4052
|
function startMockOAuth(opts?: { port?: number; clientId?: string; accessTtlMs?: number; }): Promise<MockOAuthServer>
|
|
4039
4053
|
```
|
|
4040
4054
|
|
|
4055
|
+
#### `subscribeFailedRequests` — function · src/failed-requests.ts
|
|
4056
|
+
|
|
4057
|
+
```ts
|
|
4058
|
+
function subscribeFailedRequests(l: Listener): () => void
|
|
4059
|
+
```
|
|
4060
|
+
|
|
4041
4061
|
### Types (40)
|
|
4042
4062
|
|
|
4043
4063
|
#### `BoundAppFixture` — interface · src/testing/ops.ts
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { capture, fakeApps, runOp } from "./ops.js";
|
|
|
18
18
|
export type { BoundAppFixture, EmitCall, NotifyCall, Recorder, RunOpOptions, RunOpResult } from "./ops.js";
|
|
19
19
|
export { memoryFiles } from "./files.js";
|
|
20
20
|
export { scriptedModel, defaultScriptedText } from "./llm.js";
|
|
21
|
+
/** The failed-request banner's list, for a test that asserts a click reverted and was named (runOptimistic). */
|
|
22
|
+
export { listFailedRequests, subscribeFailedRequests, __resetFailedRequests as resetFailedRequests } from "../failed-requests.js";
|
|
21
23
|
export type { ScriptedLlm, ScriptedPolicy, ScriptedReply } from "./llm.js";
|
|
22
24
|
export type { MemoryFiles, MemoryFileTree } from "./files.js";
|
|
23
25
|
export { simGmail, DEFAULT_SIM_ACCOUNT, DEFAULT_CROWD_MIX } from "./sim-gmail/index.js";
|
package/dist/testing/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export { fakeViewer, memoryDb, roleForKind } from "./db.js";
|
|
|
16
16
|
export { capture, fakeApps, runOp } from "./ops.js";
|
|
17
17
|
export { memoryFiles } from "./files.js";
|
|
18
18
|
export { scriptedModel, defaultScriptedText } from "./llm.js";
|
|
19
|
+
/** The failed-request banner's list, for a test that asserts a click reverted and was named (runOptimistic). */
|
|
20
|
+
export { listFailedRequests, subscribeFailedRequests, __resetFailedRequests as resetFailedRequests } from "../failed-requests.js";
|
|
19
21
|
export { simGmail, DEFAULT_SIM_ACCOUNT, DEFAULT_CROWD_MIX } from "./sim-gmail/index.js";
|
|
20
22
|
import http from "node:http";
|
|
21
23
|
import crypto from "node:crypto";
|
|
@@ -26,6 +26,8 @@ export declare class Personas {
|
|
|
26
26
|
crowd(n: number, mix?: CrowdMix, opts?: CrowdOptions, record?: boolean): CrowdMember[];
|
|
27
27
|
private inboundView;
|
|
28
28
|
private answerable;
|
|
29
|
+
/** Mail a machine sent (`Auto-Submitted` other than "no"): only a machine responder ignores it. */
|
|
30
|
+
private automated;
|
|
29
31
|
private select;
|
|
30
32
|
private intercept;
|
|
31
33
|
private delivered;
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* travel through the same send path as an API send, after `latencyMs` of
|
|
8
8
|
* virtual time (0 = before the sender's next request).
|
|
9
9
|
*
|
|
10
|
-
* Personas never answer
|
|
11
|
-
*
|
|
10
|
+
* Personas never answer mailer-daemon or another persona, so two scripts cannot
|
|
11
|
+
* loop. Auto-submitted mail (RFC 3834 — an agent's autonomous reply carries
|
|
12
|
+
* `Auto-Submitted: auto-replied`) is answered the way the world answers it: a
|
|
13
|
+
* PERSON (reply / opt-out) reads and answers it like any mail, a machine
|
|
14
|
+
* responder (out-of-office) stays silent, and a dead address bounces it.
|
|
12
15
|
*/
|
|
13
16
|
import { buildMime, formatAddress, headerOf, parseAddressList, textOf } from "./mime.js";
|
|
14
17
|
import { rfc2822 } from "./corpus.js";
|
|
@@ -99,13 +102,15 @@ export class Personas {
|
|
|
99
102
|
count,
|
|
100
103
|
};
|
|
101
104
|
}
|
|
102
|
-
answerable(sender, msg
|
|
105
|
+
answerable(sender, msg) {
|
|
103
106
|
if (sender.persona)
|
|
104
107
|
return false;
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
return !/mailer-daemon@|postmaster@/i.test(msg.from);
|
|
109
|
+
}
|
|
110
|
+
/** Mail a machine sent (`Auto-Submitted` other than "no"): only a machine responder ignores it. */
|
|
111
|
+
automated(node) {
|
|
107
112
|
const auto = node ? headerOf(node, "auto-submitted") : undefined;
|
|
108
|
-
return
|
|
113
|
+
return !!auto && auto.toLowerCase() !== "no";
|
|
109
114
|
}
|
|
110
115
|
select(state, inbound) {
|
|
111
116
|
for (let i = 0; i < state.steps.length; i++) {
|
|
@@ -123,8 +128,7 @@ export class Personas {
|
|
|
123
128
|
const state = this.states.get(recipient.address);
|
|
124
129
|
if (!state)
|
|
125
130
|
return false;
|
|
126
|
-
|
|
127
|
-
if (!this.answerable(sender, sentCopy, node))
|
|
131
|
+
if (!this.answerable(sender, sentCopy))
|
|
128
132
|
return false;
|
|
129
133
|
const view = this.inboundView(sender, sentCopy, state.received + 1);
|
|
130
134
|
const hit = this.select(state, view);
|
|
@@ -140,13 +144,17 @@ export class Personas {
|
|
|
140
144
|
if (!state)
|
|
141
145
|
return;
|
|
142
146
|
const node = this.world.nodeOf(mb, msg);
|
|
143
|
-
if (sender ? !this.answerable(sender, msg
|
|
147
|
+
if (sender ? !this.answerable(sender, msg) : /mailer-daemon@|postmaster@/i.test(msg.from))
|
|
144
148
|
return;
|
|
149
|
+
const auto = this.automated(node);
|
|
145
150
|
state.received++;
|
|
146
151
|
const view = this.inboundView(mb, msg, state.received);
|
|
147
152
|
const hit = this.select(state, view);
|
|
148
153
|
if (!hit)
|
|
149
154
|
return;
|
|
155
|
+
// An out-of-office never answers a machine (the loop RFC 3834 prevents); a person does.
|
|
156
|
+
if (auto && hit.step.kind === "ooo")
|
|
157
|
+
return;
|
|
150
158
|
state.consumed.add(hit.index);
|
|
151
159
|
for (const step of [hit.step, ...(hit.step.also ?? [])])
|
|
152
160
|
this.respond(mb, view, step);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esoul-sdk",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Build a full product on ExternalSoul: your own tables with per-person rules, a viewer on every seam, app roles, access levels, realtime with audiences, durable tasks, and bindings to other apps.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|