@fungi.computer/shiitake-extensions 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +262 -2
- package/dist/almanac/contract.d.ts +213 -0
- package/dist/almanac/contract.js +77 -0
- package/dist/almanac/host.d.ts +3 -0
- package/dist/almanac/host.js +171 -0
- package/dist/goal/client.d.ts +10 -0
- package/dist/goal/client.js +88 -0
- package/dist/goal/contract.d.ts +172 -0
- package/dist/goal/contract.js +76 -0
- package/dist/goal/host.d.ts +5 -0
- package/dist/goal/host.js +542 -0
- package/dist/home/contract.d.ts +33 -0
- package/dist/home/contract.js +25 -0
- package/dist/home/host.d.ts +18 -0
- package/dist/home/host.js +136 -0
- package/dist/home/routing.d.ts +3 -0
- package/dist/home/routing.js +80 -0
- package/dist/internal/catalog.d.ts +20 -0
- package/dist/internal/catalog.js +33 -0
- package/dist/internal/cron.d.ts +5 -0
- package/dist/internal/cron.js +14 -0
- package/dist/internal/history.d.ts +29 -0
- package/dist/internal/history.js +41 -0
- package/dist/loop/client.d.ts +10 -0
- package/dist/loop/client.js +89 -0
- package/dist/loop/contract.d.ts +342 -0
- package/dist/loop/contract.js +94 -0
- package/dist/loop/host.d.ts +4 -0
- package/dist/loop/host.js +637 -0
- package/dist/plan/client.d.ts +13 -0
- package/dist/plan/client.js +51 -0
- package/dist/plan/contract.d.ts +105 -0
- package/dist/plan/contract.js +38 -0
- package/dist/plan/host.d.ts +3 -0
- package/dist/plan/host.js +154 -0
- package/dist/question/client.d.ts +10 -0
- package/dist/question/client.js +28 -0
- package/dist/question/contract.d.ts +251 -0
- package/dist/question/contract.js +114 -0
- package/dist/question/host.d.ts +3 -0
- package/dist/question/host.js +997 -0
- package/dist/roster.d.ts +9 -0
- package/dist/roster.js +80 -0
- package/dist/skills/host.d.ts +7 -0
- package/dist/skills/host.js +223 -0
- package/dist/skills/source.d.ts +28 -0
- package/dist/skills/source.js +1 -0
- package/dist/subagents/client.d.ts +10 -0
- package/dist/subagents/client.js +53 -0
- package/dist/subagents/contract.d.ts +54 -0
- package/dist/subagents/contract.js +38 -0
- package/dist/subagents/host.d.ts +50 -0
- package/dist/subagents/host.js +176 -0
- package/package.json +117 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { defineExtension, } from "@fungi.computer/shiitake/extensions";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import { maximumSubagents, subagentsContract, } from "./contract.js";
|
|
5
|
+
const defaultTiming = Object.freeze({
|
|
6
|
+
refreshMs: 1_000,
|
|
7
|
+
pollMs: 5_000,
|
|
8
|
+
interestMs: 30_000,
|
|
9
|
+
});
|
|
10
|
+
/** Sessions whose rows are held at once; the least recently read go first. */
|
|
11
|
+
const cachedSessions = 64;
|
|
12
|
+
/** Owner pages scanned for one child's row: 400 conversations. */
|
|
13
|
+
const statusPages = 2;
|
|
14
|
+
export const subagentsGrant = {
|
|
15
|
+
id: subagentsContract.id,
|
|
16
|
+
capabilities: ["projection"],
|
|
17
|
+
};
|
|
18
|
+
const settledState = {
|
|
19
|
+
running: "working",
|
|
20
|
+
needs_you: "waiting",
|
|
21
|
+
done: "idle",
|
|
22
|
+
errored: "failed",
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The owner orders a page needs_you, running, then settled rows; a bounded
|
|
26
|
+
* scan finds the child's own row, and anything else is unavailable.
|
|
27
|
+
*/
|
|
28
|
+
async function findSession(agent, sessionId) {
|
|
29
|
+
let after;
|
|
30
|
+
for (let page = 0; page < statusPages; page += 1) {
|
|
31
|
+
const listed = await agent.listSessions(after);
|
|
32
|
+
const row = listed.sessions.find((item) => item.sessionId === sessionId);
|
|
33
|
+
if (row !== undefined || listed.next === undefined)
|
|
34
|
+
return row;
|
|
35
|
+
after = listed.next;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Read one child's owner-decided state; a failed read is unavailable, never idle. */
|
|
40
|
+
async function status(source, record, signal) {
|
|
41
|
+
const base = {
|
|
42
|
+
agentId: record.agentId,
|
|
43
|
+
name: record.name,
|
|
44
|
+
sessionId: record.sessionId,
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
const opened = await source.open(record.agentId, signal);
|
|
48
|
+
try {
|
|
49
|
+
if (record.sessionId === null)
|
|
50
|
+
return { ...base, state: "idle" };
|
|
51
|
+
const row = await findSession(opened.agent, record.sessionId);
|
|
52
|
+
if (row === undefined || row.state === "unavailable")
|
|
53
|
+
return { ...base, state: "unavailable" };
|
|
54
|
+
if (row.state !== "needs_you")
|
|
55
|
+
return { ...base, state: settledState[row.state] };
|
|
56
|
+
const snapshot = await opened.agent
|
|
57
|
+
.session(record.sessionId)
|
|
58
|
+
.read({ limit: 1 });
|
|
59
|
+
const attention = snapshot.attention[0]?.summary;
|
|
60
|
+
return attention === undefined
|
|
61
|
+
? { ...base, state: "waiting" }
|
|
62
|
+
: { ...base, state: "waiting", attention };
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await opened.release();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
// The row shows unavailable; the operator log keeps why.
|
|
70
|
+
Effect.runFork(Effect.logWarning("subagents.status", cause));
|
|
71
|
+
return { ...base, state: "unavailable" };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const live = (value) => value?.agents.some((agent) => agent.state === "working" || agent.state === "waiting") === true;
|
|
75
|
+
/**
|
|
76
|
+
* One activation's in-memory rows. A watcher's projection read refreshes
|
|
77
|
+
* stale rows outside the snapshot transaction; a changed or still-live result
|
|
78
|
+
* publishes one observation, whose re-read keeps interest alive.
|
|
79
|
+
*/
|
|
80
|
+
function createRoster(source, timing, publish) {
|
|
81
|
+
const entries = new Map();
|
|
82
|
+
const lifetime = new AbortController();
|
|
83
|
+
function forget(sessionId) {
|
|
84
|
+
clearTimeout(entries.get(sessionId)?.timer);
|
|
85
|
+
entries.delete(sessionId);
|
|
86
|
+
}
|
|
87
|
+
async function refresh(sessionId, entry) {
|
|
88
|
+
entry.refreshing = true;
|
|
89
|
+
clearTimeout(entry.timer);
|
|
90
|
+
entry.timer = undefined;
|
|
91
|
+
try {
|
|
92
|
+
const page = await source.read(sessionId, maximumSubagents - 1);
|
|
93
|
+
const value = {
|
|
94
|
+
agents: await Promise.all(page.agents
|
|
95
|
+
.slice(0, maximumSubagents - 1)
|
|
96
|
+
.map((record) => status(source, record, lifetime.signal))),
|
|
97
|
+
more: page.more,
|
|
98
|
+
};
|
|
99
|
+
const changed = JSON.stringify(value) !== JSON.stringify(entry.value);
|
|
100
|
+
entry.value = value;
|
|
101
|
+
if (!lifetime.signal.aborted && (changed || live(value)))
|
|
102
|
+
await publish(sessionId);
|
|
103
|
+
}
|
|
104
|
+
catch (cause) {
|
|
105
|
+
Effect.runFork(Effect.logError("subagents.refresh", cause));
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
entry.refreshedAt = Date.now();
|
|
109
|
+
entry.refreshing = false;
|
|
110
|
+
}
|
|
111
|
+
if (!lifetime.signal.aborted &&
|
|
112
|
+
entries.get(sessionId) === entry &&
|
|
113
|
+
live(entry.value) &&
|
|
114
|
+
Date.now() - entry.readAt < timing.interestMs)
|
|
115
|
+
entry.timer = setTimeout(() => {
|
|
116
|
+
void refresh(sessionId, entry);
|
|
117
|
+
}, timing.pollMs);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
read(sessionId) {
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
let entry = entries.get(sessionId);
|
|
123
|
+
if (entry === undefined) {
|
|
124
|
+
entry = {
|
|
125
|
+
value: undefined,
|
|
126
|
+
refreshedAt: Number.NEGATIVE_INFINITY,
|
|
127
|
+
readAt: now,
|
|
128
|
+
refreshing: false,
|
|
129
|
+
timer: undefined,
|
|
130
|
+
};
|
|
131
|
+
entries.set(sessionId, entry);
|
|
132
|
+
if (entries.size > cachedSessions) {
|
|
133
|
+
const oldest = [...entries].sort(([, left], [, right]) => left.readAt - right.readAt)[0];
|
|
134
|
+
if (oldest !== undefined)
|
|
135
|
+
forget(oldest[0]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
entry.readAt = now;
|
|
139
|
+
if (!entry.refreshing && now - entry.refreshedAt >= timing.refreshMs)
|
|
140
|
+
void refresh(sessionId, entry);
|
|
141
|
+
return entry.value;
|
|
142
|
+
},
|
|
143
|
+
close() {
|
|
144
|
+
lifetime.abort();
|
|
145
|
+
for (const sessionId of entries.keys())
|
|
146
|
+
forget(sessionId);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The read-only sub-agent view: the Agents this conversation sent work to or
|
|
152
|
+
* created, one row each, with their live status. It owns no relationship,
|
|
153
|
+
* no durable state and no command; the collaboration App remains the only
|
|
154
|
+
* relationship owner.
|
|
155
|
+
*/
|
|
156
|
+
export function subagentsExtension(options) {
|
|
157
|
+
const rosters = new WeakMap();
|
|
158
|
+
return defineExtension({ id: subagentsContract.id, version: "1.0.0" })
|
|
159
|
+
.config({})
|
|
160
|
+
.define({
|
|
161
|
+
hooks: {
|
|
162
|
+
start: (ctx) => Effect.acquireRelease(Effect.sync(() => {
|
|
163
|
+
const roster = createRoster(options.source, options.timing ?? defaultTiming, (sessionId) => Effect.runPromise(ctx.transact(ctx.observe(sessionId)).pipe(Effect.ignore)));
|
|
164
|
+
rosters.set(ctx, roster);
|
|
165
|
+
return roster;
|
|
166
|
+
}), (roster) => Effect.sync(() => {
|
|
167
|
+
roster.close();
|
|
168
|
+
rosters.delete(ctx);
|
|
169
|
+
})).pipe(Effect.asVoid),
|
|
170
|
+
},
|
|
171
|
+
projection: {
|
|
172
|
+
schema: subagentsContract.projection.schema,
|
|
173
|
+
read: (ctx, sessionId) => Option.fromUndefinedOr(rosters.get(ctx)?.read(sessionId)),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,120 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/shiitake-extensions",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Levi <levi@technomancy.dev>",
|
|
5
|
+
"private": false,
|
|
6
|
+
"description": "Botanical's product Shiitake extensions: goal, loop and Almanac on the generic extension surface",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./almanac": {
|
|
10
|
+
"types": "./dist/almanac/contract.d.ts",
|
|
11
|
+
"import": "./dist/almanac/contract.js"
|
|
12
|
+
},
|
|
13
|
+
"./almanac/host": {
|
|
14
|
+
"types": "./dist/almanac/host.d.ts",
|
|
15
|
+
"import": "./dist/almanac/host.js"
|
|
16
|
+
},
|
|
17
|
+
"./question": {
|
|
18
|
+
"types": "./dist/question/contract.d.ts",
|
|
19
|
+
"import": "./dist/question/contract.js"
|
|
20
|
+
},
|
|
21
|
+
"./question/client": {
|
|
22
|
+
"types": "./dist/question/client.d.ts",
|
|
23
|
+
"import": "./dist/question/client.js"
|
|
24
|
+
},
|
|
25
|
+
"./question/host": {
|
|
26
|
+
"types": "./dist/question/host.d.ts",
|
|
27
|
+
"import": "./dist/question/host.js"
|
|
28
|
+
},
|
|
29
|
+
"./goal": {
|
|
30
|
+
"types": "./dist/goal/contract.d.ts",
|
|
31
|
+
"import": "./dist/goal/contract.js"
|
|
32
|
+
},
|
|
33
|
+
"./goal/host": {
|
|
34
|
+
"types": "./dist/goal/host.d.ts",
|
|
35
|
+
"import": "./dist/goal/host.js"
|
|
36
|
+
},
|
|
37
|
+
"./loop": {
|
|
38
|
+
"types": "./dist/loop/contract.d.ts",
|
|
39
|
+
"import": "./dist/loop/contract.js"
|
|
40
|
+
},
|
|
41
|
+
"./loop/host": {
|
|
42
|
+
"types": "./dist/loop/host.d.ts",
|
|
43
|
+
"import": "./dist/loop/host.js"
|
|
44
|
+
},
|
|
45
|
+
"./roster": {
|
|
46
|
+
"types": "./dist/roster.d.ts",
|
|
47
|
+
"import": "./dist/roster.js"
|
|
48
|
+
},
|
|
49
|
+
"./plan": {
|
|
50
|
+
"types": "./dist/plan/contract.d.ts",
|
|
51
|
+
"import": "./dist/plan/contract.js"
|
|
52
|
+
},
|
|
53
|
+
"./plan/host": {
|
|
54
|
+
"types": "./dist/plan/host.d.ts",
|
|
55
|
+
"import": "./dist/plan/host.js"
|
|
56
|
+
},
|
|
57
|
+
"./skills/source": {
|
|
58
|
+
"types": "./dist/skills/source.d.ts",
|
|
59
|
+
"import": "./dist/skills/source.js"
|
|
60
|
+
},
|
|
61
|
+
"./skills": {
|
|
62
|
+
"types": "./dist/skills/host.d.ts",
|
|
63
|
+
"import": "./dist/skills/host.js"
|
|
64
|
+
},
|
|
65
|
+
"./home": {
|
|
66
|
+
"types": "./dist/home/contract.d.ts",
|
|
67
|
+
"import": "./dist/home/contract.js"
|
|
68
|
+
},
|
|
69
|
+
"./home/host": {
|
|
70
|
+
"types": "./dist/home/host.d.ts",
|
|
71
|
+
"import": "./dist/home/host.js"
|
|
72
|
+
},
|
|
73
|
+
"./goal/client": {
|
|
74
|
+
"types": "./dist/goal/client.d.ts",
|
|
75
|
+
"import": "./dist/goal/client.js"
|
|
76
|
+
},
|
|
77
|
+
"./loop/client": {
|
|
78
|
+
"types": "./dist/loop/client.d.ts",
|
|
79
|
+
"import": "./dist/loop/client.js"
|
|
80
|
+
},
|
|
81
|
+
"./plan/client": {
|
|
82
|
+
"types": "./dist/plan/client.d.ts",
|
|
83
|
+
"import": "./dist/plan/client.js"
|
|
84
|
+
},
|
|
85
|
+
"./subagents": {
|
|
86
|
+
"types": "./dist/subagents/contract.d.ts",
|
|
87
|
+
"import": "./dist/subagents/contract.js"
|
|
88
|
+
},
|
|
89
|
+
"./subagents/host": {
|
|
90
|
+
"types": "./dist/subagents/host.d.ts",
|
|
91
|
+
"import": "./dist/subagents/host.js"
|
|
92
|
+
},
|
|
93
|
+
"./subagents/client": {
|
|
94
|
+
"types": "./dist/subagents/client.d.ts",
|
|
95
|
+
"import": "./dist/subagents/client.js"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"files": [
|
|
99
|
+
"dist",
|
|
100
|
+
"README.md",
|
|
101
|
+
"LICENSE"
|
|
102
|
+
],
|
|
103
|
+
"dependencies": {
|
|
104
|
+
"@fungi.computer/almanac": "0.0.1",
|
|
105
|
+
"@fungi.computer/cairn": "0.0.1",
|
|
106
|
+
"@fungi.computer/knapsack": "0.0.2",
|
|
107
|
+
"@fungi.computer/mycelium": "0.0.2",
|
|
108
|
+
"@fungi.computer/shiitake": "0.1.0",
|
|
109
|
+
"@fungi.computer/whistle": "0.0.1",
|
|
110
|
+
"effect": "4.0.2",
|
|
111
|
+
"papaparse": "5.7.0",
|
|
112
|
+
"zod": "4.4.3",
|
|
113
|
+
"@fungi.computer/gills": "0.0.1"
|
|
114
|
+
},
|
|
115
|
+
"license": "MIT",
|
|
116
|
+
"repository": {
|
|
117
|
+
"type": "git",
|
|
118
|
+
"url": "git+https://github.com/fungi-computer/shiitake-extensions.git"
|
|
119
|
+
}
|
|
6
120
|
}
|