@fungi.computer/shiitake 0.2.0 → 0.3.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/README.md +155 -34
- package/dist/agent-control.d.ts +77 -5
- package/dist/agent-control.js +271 -65
- package/dist/client-entry.d.ts +15 -8
- package/dist/client-entry.js +7 -5
- package/dist/client-wire.d.ts +24 -8
- package/dist/client-wire.js +53 -11
- package/dist/client.d.ts +56 -11
- package/dist/client.js +1 -1
- package/dist/cloudflare.d.ts +42 -23
- package/dist/cloudflare.js +76 -54
- package/dist/cold-sessions.d.ts +28 -0
- package/dist/cold-sessions.js +1 -0
- package/dist/delegation.d.ts +214 -0
- package/dist/delegation.js +319 -0
- package/dist/effect.d.ts +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/extension-contract.d.ts +10 -2
- package/dist/extensions.d.ts +6 -2
- package/dist/fleet.d.ts +43 -9
- package/dist/fleet.js +88 -25
- package/dist/hosted.js +1 -0
- package/dist/http-routes.d.ts +113 -29
- package/dist/http-routes.js +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/internal/accepted-run-executor.d.ts +5 -0
- package/dist/internal/accepted-run-executor.js +10 -4
- package/dist/internal/admission-door.d.ts +3 -0
- package/dist/internal/admission-door.js +12 -2
- package/dist/internal/client-boundary.js +7 -1
- package/dist/internal/compaction/compaction-slot.d.ts +36 -0
- package/dist/internal/compaction/compaction-slot.js +41 -0
- package/dist/internal/compaction/compaction.d.ts +11 -8
- package/dist/internal/compaction/compaction.js +62 -34
- package/dist/internal/compaction/session-compaction.d.ts +8 -16
- package/dist/internal/compaction/session-compaction.js +358 -119
- package/dist/internal/compaction/settings.d.ts +1 -1
- package/dist/internal/compaction/settings.js +5 -0
- package/dist/internal/compaction/utils.d.ts +19 -16
- package/dist/internal/compaction/utils.js +30 -84
- package/dist/internal/contracts.d.ts +40 -13
- package/dist/internal/coordinator.js +28 -10
- package/dist/internal/effect-client.d.ts +4 -2
- package/dist/internal/effect-client.js +29 -0
- package/dist/internal/effect-contracts.d.ts +17 -4
- package/dist/internal/extension-runtime.d.ts +5 -0
- package/dist/internal/extension-runtime.js +184 -89
- package/dist/internal/host-meta.d.ts +7 -0
- package/dist/internal/model-acquisition.js +7 -1
- package/dist/internal/model-preference.d.ts +4 -2
- package/dist/internal/model-preference.js +2 -1
- package/dist/internal/promise-facade.js +4 -16
- package/dist/internal/run-failure-reason.d.ts +10 -0
- package/dist/internal/run-failure-reason.js +19 -0
- package/dist/internal/run-scope.d.ts +4 -1
- package/dist/internal/run-scope.js +38 -5
- package/dist/internal/runtime-coordinator.d.ts +2 -1
- package/dist/internal/runtime-coordinator.js +65 -4
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +42 -6
- package/dist/internal/session-context.d.ts +1 -2
- package/dist/internal/session-context.js +8 -30
- package/dist/internal/session-status.d.ts +2 -1
- package/dist/internal/session-status.js +4 -1
- package/dist/internal/woodstock/client-projection.d.ts +6 -5
- package/dist/internal/woodstock/client-projection.js +79 -53
- package/dist/internal/woodstock/cold-sessions.d.ts +4 -0
- package/dist/internal/woodstock/cold-sessions.js +90 -0
- package/dist/internal/woodstock/extension-store.d.ts +2 -0
- package/dist/internal/woodstock/extension-store.js +4 -0
- package/dist/internal/woodstock/fleet-store.d.ts +5 -1
- package/dist/internal/woodstock/fleet-store.js +17 -5
- package/dist/internal/woodstock/index.d.ts +11 -3
- package/dist/internal/woodstock/index.js +33 -14
- package/dist/internal/woodstock/progress.d.ts +14 -1
- package/dist/internal/woodstock/progress.js +97 -19
- package/dist/internal/woodstock/run-recovery.js +6 -4
- package/dist/internal/woodstock/session-attention.d.ts +1 -1
- package/dist/internal/woodstock/session-attention.js +2 -2
- package/dist/internal/woodstock/session-entry.d.ts +110 -43
- package/dist/internal/woodstock/session-entry.js +63 -20
- package/dist/internal/woodstock/session-inbox.js +2 -0
- package/dist/internal/woodstock/session-store.d.ts +104 -24
- package/dist/internal/woodstock/session-store.js +937 -246
- package/dist/internal/woodstock/storage-admission.d.ts +8 -8
- package/dist/internal/woodstock/storage-admission.js +8 -34
- package/dist/internal/woodstock/storage-conformance.d.ts +15 -0
- package/dist/internal/woodstock/storage-conformance.js +489 -0
- package/dist/model-preference.d.ts +2 -2
- package/dist/model-preference.js +2 -2
- package/dist/node.js +1 -1
- package/dist/remote-client.js +8 -1
- package/dist/session-attention.d.ts +8 -1
- package/dist/session-attention.js +6 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -0
- package/docs/api/README.md +18 -16
- package/docs/api/agent-control.md +171 -1
- package/docs/api/client-entry.md +26 -10
- package/docs/api/client.md +153 -37
- package/docs/api/cloudflare.md +76 -23
- package/docs/api/delegation.md +485 -0
- package/docs/api/effect.md +20 -2
- package/docs/api/extensions.md +21 -9
- package/docs/api/fleet.md +159 -104
- package/docs/api/http.md +4 -1
- package/docs/api/index.md +116 -20
- package/docs/api/internal/woodstock/storage-conformance.md +81 -0
- package/docs/api/model-preference.md +27 -10
- package/docs/api/testing.md +16 -4
- package/package.json +26 -14
- package/dist/internal/browser-sessions.d.ts +0 -31
- package/dist/internal/browser-sessions.js +0 -173
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { ShiitakeError } from "../../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* The store keeper's read of cold Sessions. It is one indexed keyset page over
|
|
5
|
+
* the inbox, with each Session's bytes summed from its own rows, so no counter
|
|
6
|
+
* can drift and nothing needs reconciling.
|
|
7
|
+
*/
|
|
8
|
+
const defaultLimit = 50;
|
|
9
|
+
const maxLimit = 200;
|
|
10
|
+
const closed = { onExcessProperty: "error" };
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
const timestamp = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
13
|
+
const boundedId = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.makeFilter((value) => encoder.encode(value).byteLength <= 256)));
|
|
14
|
+
const RequestSchema = Schema.Struct({
|
|
15
|
+
limit: Schema.optionalKey(Schema.Int.pipe(Schema.check(Schema.isBetween({ minimum: 1, maximum: maxLimit })))),
|
|
16
|
+
after: Schema.optionalKey(Schema.String),
|
|
17
|
+
});
|
|
18
|
+
const decodeRequest = Schema.decodeUnknownResult(RequestSchema, closed);
|
|
19
|
+
const CursorSchema = Schema.fromJsonString(Schema.Tuple([timestamp, boundedId]));
|
|
20
|
+
const decodeCursor = Schema.decodeUnknownResult(CursorSchema);
|
|
21
|
+
const encodeCursor = Schema.encodeSync(CursorSchema);
|
|
22
|
+
const RowSchema = Schema.Struct({
|
|
23
|
+
session_id: boundedId,
|
|
24
|
+
last_activity_at: timestamp,
|
|
25
|
+
state: Schema.Literals(["needs_you", "running", "errored", "done"]),
|
|
26
|
+
bytes: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
27
|
+
});
|
|
28
|
+
const decodeRow = Schema.decodeUnknownResult(RowSchema, closed);
|
|
29
|
+
const invalid = (message) => new ShiitakeError("contract", "invalid", message);
|
|
30
|
+
const payloadBytes = (table, column) => `(SELECT COALESCE(SUM(LENGTH(CAST(${column} AS BLOB))), 0) FROM ${table} WHERE session_id = p.session_id)`;
|
|
31
|
+
/**
|
|
32
|
+
* The coldest-first window is chosen from 'session_inbox' alone; stored bytes
|
|
33
|
+
* are summed only for its first 'limit' rows. The extra lookahead row only
|
|
34
|
+
* proves another page exists and is discarded, so it is never measured.
|
|
35
|
+
*/
|
|
36
|
+
const selectColdSessions = (where) => `
|
|
37
|
+
WITH p AS (
|
|
38
|
+
SELECT i.session_id, i.last_activity_at, i.state,
|
|
39
|
+
ROW_NUMBER() OVER (ORDER BY i.last_activity_at ASC, i.session_id ASC) AS position
|
|
40
|
+
FROM session_inbox i
|
|
41
|
+
${where}
|
|
42
|
+
ORDER BY i.last_activity_at ASC, i.session_id ASC LIMIT ?
|
|
43
|
+
)
|
|
44
|
+
SELECT p.session_id, p.last_activity_at, p.state,
|
|
45
|
+
CASE WHEN p.position <= ? THEN
|
|
46
|
+
${payloadBytes("session_entries", "data_json")}
|
|
47
|
+
+ ${payloadBytes("session_command_admissions", "data_json")}
|
|
48
|
+
+ ${payloadBytes("session_attention", "value_json")}
|
|
49
|
+
+ ${payloadBytes("session_tool_grants", "grants_json")}
|
|
50
|
+
ELSE 0 END AS bytes
|
|
51
|
+
FROM p ORDER BY p.position`;
|
|
52
|
+
/** Read one bounded page of Sessions with stored bytes, coldest first. */
|
|
53
|
+
export function readColdSessionPageOwned(owner, request) {
|
|
54
|
+
const decodedRequest = decodeRequest(request);
|
|
55
|
+
if (decodedRequest._tag === "Failure")
|
|
56
|
+
throw invalid("Cold Session page request is invalid");
|
|
57
|
+
const limit = decodedRequest.success.limit ?? defaultLimit;
|
|
58
|
+
const after = decodedRequest.success.after;
|
|
59
|
+
let cursor;
|
|
60
|
+
if (after !== undefined) {
|
|
61
|
+
const decoded = decodeCursor(after);
|
|
62
|
+
if (decoded._tag === "Failure")
|
|
63
|
+
throw invalid("Cold Session cursor is invalid");
|
|
64
|
+
cursor = decoded.success;
|
|
65
|
+
}
|
|
66
|
+
// One extra row tells a full last page from a page with more behind it.
|
|
67
|
+
const raws = cursor === undefined
|
|
68
|
+
? owner.sql.exec(selectColdSessions(""), limit + 1, limit).toArray()
|
|
69
|
+
: owner.sql
|
|
70
|
+
.exec(selectColdSessions(`WHERE i.last_activity_at > ?
|
|
71
|
+
OR (i.last_activity_at = ? AND i.session_id > ?)`), cursor[0], cursor[0], cursor[1], limit + 1, limit)
|
|
72
|
+
.toArray();
|
|
73
|
+
const sessions = [];
|
|
74
|
+
for (const raw of raws.slice(0, limit)) {
|
|
75
|
+
const decoded = decodeRow(raw);
|
|
76
|
+
if (decoded._tag === "Failure")
|
|
77
|
+
throw new ShiitakeError("store", "unavailable", "Stored Session row is invalid");
|
|
78
|
+
const row = decoded.success;
|
|
79
|
+
sessions.push({
|
|
80
|
+
sessionId: row.session_id,
|
|
81
|
+
bytes: row.bytes,
|
|
82
|
+
lastActivityAt: row.last_activity_at,
|
|
83
|
+
idle: row.state === "done" || row.state === "errored",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const last = sessions.at(-1);
|
|
87
|
+
return raws.length > limit && last !== undefined
|
|
88
|
+
? { sessions, next: encodeCursor([last.lastActivityAt, last.sessionId]) }
|
|
89
|
+
: { sessions };
|
|
90
|
+
}
|
|
@@ -43,6 +43,8 @@ export declare function writeStatus(owner: SqliteOwner, extensionId: string, sta
|
|
|
43
43
|
export declare function writeTimer(owner: SqliteOwner, extensionId: string, key: string, dueAt: number): void;
|
|
44
44
|
/** Delete one timer; absent keys are a no-op. */
|
|
45
45
|
export declare function deleteTimer(owner: SqliteOwner, extensionId: string, key: string): void;
|
|
46
|
+
/** Release all timer registrations when their extension leaves the roster. */
|
|
47
|
+
export declare function deleteExtensionTimers(owner: SqliteOwner, extensionId: string): void;
|
|
46
48
|
/** Read one timer's due instant. */
|
|
47
49
|
export declare function readTimer(owner: SqliteOwner, extensionId: string, key: string): number | undefined;
|
|
48
50
|
/** Due timers of the given extensions, oldest first, bounded by `limit`. */
|
|
@@ -194,6 +194,10 @@ export function writeTimer(owner, extensionId, key, dueAt) {
|
|
|
194
194
|
export function deleteTimer(owner, extensionId, key) {
|
|
195
195
|
owner.sql.exec("DELETE FROM shiitake_extension_timers WHERE extension_id = ? AND key = ?", extensionId, key);
|
|
196
196
|
}
|
|
197
|
+
/** Release all timer registrations when their extension leaves the roster. */
|
|
198
|
+
export function deleteExtensionTimers(owner, extensionId) {
|
|
199
|
+
owner.sql.exec("DELETE FROM shiitake_extension_timers WHERE extension_id = ?", extensionId);
|
|
200
|
+
}
|
|
197
201
|
/** Read one timer's due instant. */
|
|
198
202
|
export function readTimer(owner, extensionId, key) {
|
|
199
203
|
const rows = owner.sql
|
|
@@ -10,7 +10,7 @@ export declare function createFleetDirectoryStore(sql: WatchdogSqliteOwner["sql"
|
|
|
10
10
|
findByName: (name: string) => {
|
|
11
11
|
readonly [x: string]: string | number | ArrayBuffer | null;
|
|
12
12
|
} | undefined;
|
|
13
|
-
reserve(agentId: string, name: string, actorUserId: string, now: number, requestId: string, configuration: string): void;
|
|
13
|
+
reserve(agentId: string, name: string, actorUserId: string, now: number, requestId: string, configuration: string, createdBy: string | null): void;
|
|
14
14
|
list: () => readonly {
|
|
15
15
|
readonly [x: string]: string | number | ArrayBuffer | null;
|
|
16
16
|
}[];
|
|
@@ -27,6 +27,10 @@ export declare function createFleetDelegationStore(storage: WatchdogSqliteOwner)
|
|
|
27
27
|
read: (requestId: string) => {
|
|
28
28
|
readonly [x: string]: string | number | ArrayBuffer | null;
|
|
29
29
|
} | undefined;
|
|
30
|
+
/** Every delegation this owner has sent and had accepted, newest first. */
|
|
31
|
+
sent: () => readonly {
|
|
32
|
+
readonly [x: string]: string | number | ArrayBuffer | null;
|
|
33
|
+
}[];
|
|
30
34
|
reserve(requestId: string, input: string, mail: string): void;
|
|
31
35
|
recordReceipt(requestId: string, receipt: string): void;
|
|
32
36
|
};
|
|
@@ -6,9 +6,17 @@ export function createFleetDirectoryStore(sql) {
|
|
|
6
6
|
agent_id TEXT PRIMARY KEY,
|
|
7
7
|
name TEXT NOT NULL UNIQUE,
|
|
8
8
|
actor_user_id TEXT NOT NULL,
|
|
9
|
+
created_by TEXT,
|
|
9
10
|
created_at INTEGER NOT NULL,
|
|
10
11
|
updated_at INTEGER NOT NULL
|
|
11
12
|
) STRICT`);
|
|
13
|
+
// Existing pre-release Agents have no recorded maker. Never infer one from
|
|
14
|
+
// correspondence or actor_user_id (the human credential owner).
|
|
15
|
+
const columns = input.sql
|
|
16
|
+
.exec("PRAGMA table_info(hub_team_agents)")
|
|
17
|
+
.toArray();
|
|
18
|
+
if (!columns.some((column) => column.name === "created_by"))
|
|
19
|
+
input.sql.exec("ALTER TABLE hub_team_agents ADD COLUMN created_by TEXT");
|
|
12
20
|
input.sql.exec(`CREATE TABLE IF NOT EXISTS hub_team_agent_create_receipts (
|
|
13
21
|
request_id TEXT PRIMARY KEY,
|
|
14
22
|
actor_user_id TEXT NOT NULL,
|
|
@@ -28,21 +36,21 @@ export function createFleetDirectoryStore(sql) {
|
|
|
28
36
|
input.sql.exec("INSERT OR IGNORE INTO hub_team_agent_erasure_scope(singleton,complete) VALUES (1,1)");
|
|
29
37
|
return {
|
|
30
38
|
read: (agentId) => input.sql
|
|
31
|
-
.exec("SELECT agent_id, name, actor_user_id, created_at, updated_at FROM hub_team_agents WHERE agent_id = ?", agentId)
|
|
39
|
+
.exec("SELECT agent_id, name, actor_user_id, created_by, created_at, updated_at FROM hub_team_agents WHERE agent_id = ?", agentId)
|
|
32
40
|
.toArray()[0],
|
|
33
41
|
readReceipt: (requestId) => input.sql
|
|
34
42
|
.exec("SELECT request_id, actor_user_id, name, agent_id, computer_mode FROM hub_team_agent_create_receipts WHERE request_id = ?", requestId)
|
|
35
43
|
.toArray()[0],
|
|
36
44
|
findByName: (name) => input.sql
|
|
37
|
-
.exec("SELECT agent_id, name, actor_user_id, created_at, updated_at FROM hub_team_agents WHERE name = ?", name)
|
|
45
|
+
.exec("SELECT agent_id, name, actor_user_id, created_by, created_at, updated_at FROM hub_team_agents WHERE name = ?", name)
|
|
38
46
|
.toArray()[0],
|
|
39
|
-
reserve(agentId, name, actorUserId, now, requestId, configuration) {
|
|
40
|
-
input.sql.exec("INSERT INTO hub_team_agents (agent_id, name, actor_user_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", agentId, name, actorUserId, now, now);
|
|
47
|
+
reserve(agentId, name, actorUserId, now, requestId, configuration, createdBy) {
|
|
48
|
+
input.sql.exec("INSERT INTO hub_team_agents (agent_id, name, actor_user_id, created_by, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", agentId, name, actorUserId, createdBy, now, now);
|
|
41
49
|
input.sql.exec("INSERT INTO hub_team_agent_erasure_ids(agent_id) VALUES (?)", agentId);
|
|
42
50
|
input.sql.exec("INSERT INTO hub_team_agent_create_receipts (request_id, actor_user_id, name, agent_id, computer_mode) VALUES (?, ?, ?, ?, ?)", requestId, actorUserId, name, agentId, configuration);
|
|
43
51
|
},
|
|
44
52
|
list: () => input.sql
|
|
45
|
-
.exec("SELECT agent_id, name, actor_user_id, created_at, updated_at FROM hub_team_agents ORDER BY created_at DESC, agent_id DESC")
|
|
53
|
+
.exec("SELECT agent_id, name, actor_user_id, created_by, created_at, updated_at FROM hub_team_agents ORDER BY created_at DESC, agent_id DESC")
|
|
46
54
|
.toArray(),
|
|
47
55
|
rename(agentId, name, now) {
|
|
48
56
|
input.sql.exec("UPDATE hub_team_agents SET name = ?, updated_at = ? WHERE agent_id = ?", name, now, agentId);
|
|
@@ -79,6 +87,10 @@ export function createFleetDelegationStore(storage) {
|
|
|
79
87
|
read: (requestId) => storage.sql
|
|
80
88
|
.exec("SELECT input, mail, receipt FROM shiitake_fleet_delegations WHERE request_id = ?", requestId)
|
|
81
89
|
.toArray()[0],
|
|
90
|
+
/** Every delegation this owner has sent and had accepted, newest first. */
|
|
91
|
+
sent: () => storage.sql
|
|
92
|
+
.exec("SELECT input, mail, receipt FROM shiitake_fleet_delegations WHERE receipt IS NOT NULL ORDER BY rowid DESC")
|
|
93
|
+
.toArray(),
|
|
82
94
|
reserve(requestId, input, mail) {
|
|
83
95
|
storage.transactionSync(() => {
|
|
84
96
|
storage.sql.exec("INSERT OR IGNORE INTO shiitake_fleet_delegations(request_id, input, mail) VALUES (?, ?, ?)", requestId, input, mail);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { SandboxFailureEvent } from "@fungi.computer/mycelium";
|
|
2
2
|
import type { Json } from "../../extensions.js";
|
|
3
|
-
import type { MuleError,
|
|
3
|
+
import type { MuleError, MuleEvent, MuleIdentity } from "@fungi.computer/mule/effect";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
5
|
import type * as Scope from "effect/Scope";
|
|
6
|
+
import type { ColdSessionPage, ColdSessionPageRequest } from "../../cold-sessions.js";
|
|
6
7
|
import type { ReadOptions, SessionExtensionSlots, SessionSnapshot } from "../../client.js";
|
|
7
8
|
import { ShiitakeError } from "../../errors.js";
|
|
8
9
|
import type { CommandSettlement, SqliteOwner } from "../contracts.js";
|
|
@@ -11,6 +12,7 @@ import { type ClientReplayPage } from "./client-projection.js";
|
|
|
11
12
|
import { type LiveRetryEvent } from "./session-entry.js";
|
|
12
13
|
import { type CompactionNoticeInput, type AcceptedCommand, type EnqueueAcceptedWork, type AppendCompactionInput, type PreparedSessionRun, type RunContract, type RecordAcceptedCancellation, type SessionQueueHead, type StoredSessionRow } from "./session-store.js";
|
|
13
14
|
export * from "./extension-store.js";
|
|
15
|
+
export { readLatestPendingPrompt } from "./client-projection.js";
|
|
14
16
|
export { createFleetDirectoryStore, createFleetDelegationStore, } from "./fleet-store.js";
|
|
15
17
|
export * from "./session-attention.js";
|
|
16
18
|
export { deliverSessionGrants, prepareSessionGrants, readSessionGrants, type GrantsDelivery, type GrantsDeliveryResult, } from "./session-grants.js";
|
|
@@ -26,6 +28,8 @@ export * from "./storage-admission.js";
|
|
|
26
28
|
export declare function appendCompactionNotice(owner: SqliteOwner, input: CompactionNoticeInput): StoredSessionRow;
|
|
27
29
|
/** Append compaction through Woodstock's ordinary owner-observation boundary. */
|
|
28
30
|
export declare function appendCompactionTransition(owner: SqliteOwner, input: AppendCompactionInput): StoredSessionRow;
|
|
31
|
+
/** Place a background summary, head-checked in the append transaction. */
|
|
32
|
+
export declare function placeCompactionTransition(owner: SqliteOwner, input: AppendCompactionInput): StoredSessionRow | "stale";
|
|
29
33
|
/** Prepare Woodstock's retained Session and private admission schema. */
|
|
30
34
|
export declare function prepareWoodstockSchema(owner: SqliteOwner): Effect.Effect<void, ShiitakeError>;
|
|
31
35
|
/** Atomically cancel one exact owner-issued command. */
|
|
@@ -52,6 +56,7 @@ export declare function settleControls(owner: SqliteOwner, input: Readonly<{
|
|
|
52
56
|
sessionId: string;
|
|
53
57
|
parentWorkId: string;
|
|
54
58
|
outcome: "completed" | "cancelled" | "failed" | "interrupted";
|
|
59
|
+
diagnostic: string | undefined;
|
|
55
60
|
enqueue: EnqueueAcceptedWork;
|
|
56
61
|
}>): Effect.Effect<void, ShiitakeError>;
|
|
57
62
|
/** Resolve Session-scoped abort to private work and record it atomically. */
|
|
@@ -127,6 +132,8 @@ export declare function deleteSession(owner: SqliteOwner, input: Readonly<{
|
|
|
127
132
|
}>): Effect.Effect<boolean, ShiitakeError>;
|
|
128
133
|
/** Read a page of stored conversation facts; status needs the Session owner. */
|
|
129
134
|
export declare function readSessionFactPage(owner: SqliteOwner, after?: string): Effect.Effect<import("./session-store.js").StoredSessionSummaryPage, ShiitakeError>;
|
|
135
|
+
/** Read one bounded page of Sessions with stored bytes, coldest first. */
|
|
136
|
+
export declare function readColdSessionPage(owner: SqliteOwner, request: ColdSessionPageRequest): Effect.Effect<ColdSessionPage, ShiitakeError>;
|
|
130
137
|
/** Read one bounded inbox page in tier, then newest-activity order. */
|
|
131
138
|
export declare function readSessionInbox(owner: SqliteOwner, after: string | undefined): Effect.Effect<import("../../client.js").SessionSummaryPage, ShiitakeError>;
|
|
132
139
|
/** Read one strict-after page from Woodstock's selected lineage. */
|
|
@@ -149,7 +156,7 @@ export type SettledRunFacts = Readonly<{
|
|
|
149
156
|
export declare function readExtensionAdmissionOrigin(owner: SqliteOwner, sessionId: string, commandFactId: string, workId: string): ExtensionAdmissionOrigin;
|
|
150
157
|
/** Read the terminal and admission origin of one settled accepted command. */
|
|
151
158
|
export declare function readSettledRunFacts(owner: SqliteOwner, sessionId: string, commandFactId: string, workId: string): SettledRunFacts;
|
|
152
|
-
/** Record
|
|
159
|
+
/** Record settlement only when Mule has no matching recorded terminal. */
|
|
153
160
|
export declare function recordWorkSettlement(owner: SqliteOwner, input: Readonly<{
|
|
154
161
|
sessionId: string;
|
|
155
162
|
commandFactId: string;
|
|
@@ -160,11 +167,12 @@ export declare function recordWorkSettlement(owner: SqliteOwner, input: Readonly
|
|
|
160
167
|
}>): Effect.Effect<void, ShiitakeError>;
|
|
161
168
|
/** Record one retained live Pi retry observation on the Session lineage. */
|
|
162
169
|
export declare function recordLiveRetryEvent(owner: SqliteOwner, accepted: AcceptedCommand, event: LiveRetryEvent, turn: MuleIdentity): Effect.Effect<void, MuleError>;
|
|
170
|
+
export type MuleRecorder = (event: MuleEvent, diagnostic?: string) => Effect.Effect<void, MuleError>;
|
|
163
171
|
/**
|
|
164
172
|
* Open the one scoped listener that projects Mule's exact event stream into
|
|
165
173
|
* retained Session facts and bounded process-local progress.
|
|
166
174
|
*/
|
|
167
|
-
export declare function openMuleRecorder(owner: SqliteOwner, accepted: AcceptedCommand, contract: RunContract): Effect.Effect<
|
|
175
|
+
export declare function openMuleRecorder(owner: SqliteOwner, accepted: AcceptedCommand, contract: RunContract): Effect.Effect<MuleRecorder, never, Scope.Scope>;
|
|
168
176
|
export { declareSessionDocument } from "./session-documents.js";
|
|
169
177
|
/** Read exact settled work facts through Woodstock's native command/work indexes. */
|
|
170
178
|
export declare function readCommandSettlement(owner: SqliteOwner, sessionId: string, commandId: string): Effect.Effect<CommandSettlement | undefined, ShiitakeError>;
|
|
@@ -2,13 +2,15 @@ import * as Effect from "effect/Effect";
|
|
|
2
2
|
import { ShiitakeError } from "../../errors.js";
|
|
3
3
|
import { decodeClientReadOptions, decodeClientSessionCreationRequestId, decodeClientSessionId, } from "../client-boundary.js";
|
|
4
4
|
import { publishOwnerObservation } from "../observation-kernel.js";
|
|
5
|
+
import { readColdSessionPageOwned } from "./cold-sessions.js";
|
|
5
6
|
import { diagnosticsByWorkId, projectCommandSettlement, readClientReplayPage, readClientSnapshot, } from "./client-projection.js";
|
|
6
7
|
import { sessionIds } from "./identities.js";
|
|
7
|
-
import { openProgress } from "./progress.js";
|
|
8
|
+
import { openProgress, openToolProgress } from "./progress.js";
|
|
8
9
|
import { encodeStoredSessionEntry, } from "./session-entry.js";
|
|
9
|
-
import { appendSessionEntry, appendCompactionTransitionOwned, appendCompactionNoticeOwned, acceptSessionAbort, acceptSessionCancellation, completeSessionLeaf, prepareSessionRun, prepareSessionStore, readSessionEntriesForWork, readSessionCommandAdmission, settleSessionControls, takePendingSessionControls, readSessionEntryById, selectSessionBranch, forkSessionAt, readStoredSessionSummary as readSessionSummaryOwned, readStoredSessionFactPage, createStoredSession, deleteStoredSession, appendOperatorEntry, } from "./session-store.js";
|
|
10
|
+
import { appendSessionEntry, appendCompactionTransitionOwned, appendCompactionNoticeOwned, placeCompactionTransitionOwned, acceptSessionAbort, acceptSessionCancellation, completeSessionLeaf, prepareSessionRun, prepareSessionStore, readSessionEntriesForWork, readSessionCommandAdmission, settleSessionControls, takePendingSessionControls, readSessionEntryById, selectSessionBranch, forkSessionAt, readStoredSessionSummary as readSessionSummaryOwned, readStoredSessionFactPage, createStoredSession, deleteStoredSession, appendOperatorEntry, } from "./session-store.js";
|
|
10
11
|
import { readSessionInboxPage, recordSessionInboxDeletion, } from "./session-inbox.js";
|
|
11
12
|
export * from "./extension-store.js";
|
|
13
|
+
export { readLatestPendingPrompt } from "./client-projection.js";
|
|
12
14
|
export { createFleetDirectoryStore, createFleetDelegationStore, } from "./fleet-store.js";
|
|
13
15
|
export * from "./session-attention.js";
|
|
14
16
|
export { deliverSessionGrants, prepareSessionGrants, readSessionGrants, } from "./session-grants.js";
|
|
@@ -32,6 +34,13 @@ export function appendCompactionTransition(owner, input) {
|
|
|
32
34
|
publishOwnerObservation(owner, input.sessionId);
|
|
33
35
|
return row;
|
|
34
36
|
}
|
|
37
|
+
/** Place a background summary, head-checked in the append transaction. */
|
|
38
|
+
export function placeCompactionTransition(owner, input) {
|
|
39
|
+
const placed = placeCompactionTransitionOwned(owner, input);
|
|
40
|
+
if (placed !== "stale")
|
|
41
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
42
|
+
return placed;
|
|
43
|
+
}
|
|
35
44
|
function rejected(message) {
|
|
36
45
|
return new ShiitakeError("store", "rejected", message);
|
|
37
46
|
}
|
|
@@ -207,6 +216,10 @@ export function deleteSession(owner, input) {
|
|
|
207
216
|
export function readSessionFactPage(owner, after) {
|
|
208
217
|
return storeEffect(() => readStoredSessionFactPage(owner, after === undefined ? undefined : decodeClientSessionId(after)));
|
|
209
218
|
}
|
|
219
|
+
/** Read one bounded page of Sessions with stored bytes, coldest first. */
|
|
220
|
+
export function readColdSessionPage(owner, request) {
|
|
221
|
+
return storeEffect(() => readColdSessionPageOwned(owner, request));
|
|
222
|
+
}
|
|
210
223
|
/** Read one bounded inbox page in tier, then newest-activity order. */
|
|
211
224
|
export function readSessionInbox(owner, after) {
|
|
212
225
|
return storeEffect(() => readSessionInboxPage(owner, after));
|
|
@@ -300,7 +313,7 @@ export function readSettledRunFacts(owner, sessionId, commandFactId, workId) {
|
|
|
300
313
|
toolCalls,
|
|
301
314
|
});
|
|
302
315
|
}
|
|
303
|
-
/** Record
|
|
316
|
+
/** Record settlement only when Mule has no matching recorded terminal. */
|
|
304
317
|
export function recordWorkSettlement(owner, input) {
|
|
305
318
|
return storeEffect(() => {
|
|
306
319
|
const admission = readSessionCommandAdmission(owner, input.sessionId, input.commandFactId);
|
|
@@ -319,9 +332,9 @@ export function recordWorkSettlement(owner, input) {
|
|
|
319
332
|
});
|
|
320
333
|
});
|
|
321
334
|
}
|
|
322
|
-
function recordLifecycle(owner, accepted, contract, event) {
|
|
335
|
+
function recordLifecycle(owner, accepted, contract, event, diagnostic) {
|
|
323
336
|
return storeEffect(() => {
|
|
324
|
-
|
|
337
|
+
let entry = event.type === "run_start"
|
|
325
338
|
? {
|
|
326
339
|
kind: "run_started",
|
|
327
340
|
workId: accepted.commandId,
|
|
@@ -342,6 +355,8 @@ function recordLifecycle(owner, accepted, contract, event) {
|
|
|
342
355
|
terminal: event.terminal,
|
|
343
356
|
outcome: event.outcome,
|
|
344
357
|
};
|
|
358
|
+
if (entry.kind === "run_terminal" && diagnostic !== undefined)
|
|
359
|
+
entry = { ...entry, diagnostic };
|
|
345
360
|
appendRecordedEntry(owner, accepted, entry, ({ entry: current }) => {
|
|
346
361
|
if (current.kind !== entry.kind)
|
|
347
362
|
return false;
|
|
@@ -422,18 +437,19 @@ function recordMessageEnd(owner, accepted, event) {
|
|
|
422
437
|
message: event.message,
|
|
423
438
|
value: event.value,
|
|
424
439
|
};
|
|
440
|
+
const completed = event.data === undefined ? entry : { ...entry, data: event.data };
|
|
425
441
|
const delivery = owner.transactionSync(() => {
|
|
426
442
|
const existing = readSessionEntriesForWork(owner, accepted.sessionId, accepted.commandId).find(({ entry: current }) => (current.kind === "message_start" || current.kind === "message") &&
|
|
427
443
|
sameIdentity(current.message, event.message));
|
|
428
444
|
if (existing?.entry.kind === "message") {
|
|
429
|
-
if (!sameEntry(existing.entry,
|
|
445
|
+
if (!sameEntry(existing.entry, completed))
|
|
430
446
|
throw conflict("Mule completed-message identity conflicts");
|
|
431
447
|
return "replay";
|
|
432
448
|
}
|
|
433
449
|
completeSessionLeaf(owner, {
|
|
434
450
|
sessionId: accepted.sessionId,
|
|
435
451
|
id: event.message.value,
|
|
436
|
-
entry,
|
|
452
|
+
entry: completed,
|
|
437
453
|
});
|
|
438
454
|
return "new";
|
|
439
455
|
});
|
|
@@ -460,6 +476,7 @@ function recordToolIntent(owner, accepted, contract, event) {
|
|
|
460
476
|
}
|
|
461
477
|
function recordToolOutcome(owner, accepted, event) {
|
|
462
478
|
return storeEffect(() => {
|
|
479
|
+
const { data, ...result } = event.result;
|
|
463
480
|
const entry = {
|
|
464
481
|
kind: "tool_outcome",
|
|
465
482
|
workId: accepted.commandId,
|
|
@@ -467,9 +484,9 @@ function recordToolOutcome(owner, accepted, event) {
|
|
|
467
484
|
turn: event.turn,
|
|
468
485
|
toolCall: event.toolCall,
|
|
469
486
|
call: event.call,
|
|
470
|
-
result
|
|
487
|
+
result,
|
|
471
488
|
};
|
|
472
|
-
appendRecordedEntry(owner, accepted, entry, ({ entry: current }) => current.kind === "tool_outcome" &&
|
|
489
|
+
appendRecordedEntry(owner, accepted, data === undefined ? entry : { ...entry, data }, ({ entry: current }) => current.kind === "tool_outcome" &&
|
|
473
490
|
sameIdentity(current.toolCall, event.toolCall));
|
|
474
491
|
});
|
|
475
492
|
}
|
|
@@ -505,14 +522,15 @@ const listenerFailure = () => ({
|
|
|
505
522
|
export function openMuleRecorder(owner, accepted, contract) {
|
|
506
523
|
return Effect.gen(function* () {
|
|
507
524
|
const progress = yield* openProgress(owner, accepted, (event) => recordMessageEnd(owner, accepted, event));
|
|
508
|
-
const
|
|
525
|
+
const toolProgress = yield* openToolProgress(owner, accepted);
|
|
526
|
+
const record = (event, diagnostic) => {
|
|
509
527
|
switch (event.type) {
|
|
510
528
|
case "model_prompt":
|
|
511
529
|
return recordModelPrompt(owner, accepted, contract, event);
|
|
512
530
|
case "run_start":
|
|
513
531
|
case "turn_start":
|
|
514
532
|
case "run_end":
|
|
515
|
-
return recordLifecycle(owner, accepted, contract, event);
|
|
533
|
+
return recordLifecycle(owner, accepted, contract, event, diagnostic);
|
|
516
534
|
case "message_start":
|
|
517
535
|
if (event.value.role === "user")
|
|
518
536
|
return Effect.void;
|
|
@@ -528,15 +546,16 @@ export function openMuleRecorder(owner, accepted, contract) {
|
|
|
528
546
|
? progress.terminal(event)
|
|
529
547
|
: recordMessageEnd(owner, accepted, event);
|
|
530
548
|
case "tool_execution_start":
|
|
531
|
-
return recordToolIntent(owner, accepted, contract, event);
|
|
549
|
+
return recordToolIntent(owner, accepted, contract, event).pipe(Effect.andThen(toolProgress.started(event)));
|
|
532
550
|
case "tool_execution_end":
|
|
533
|
-
return recordToolOutcome(owner, accepted, event);
|
|
551
|
+
return recordToolOutcome(owner, accepted, event).pipe(Effect.andThen(toolProgress.terminal(event)));
|
|
534
552
|
case "tool_execution_update":
|
|
553
|
+
return toolProgress.update(event);
|
|
535
554
|
case "turn_end":
|
|
536
555
|
return Effect.void;
|
|
537
556
|
}
|
|
538
557
|
};
|
|
539
|
-
return (event) => record(event).pipe(Effect.mapError(listenerFailure));
|
|
558
|
+
return (event, diagnostic) => record(event, diagnostic).pipe(Effect.mapError(listenerFailure));
|
|
540
559
|
});
|
|
541
560
|
}
|
|
542
561
|
export { declareSessionDocument } from "./session-documents.js";
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import type { MuleEvent } from "@fungi.computer/mule";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import type * as Scope from "effect/Scope";
|
|
4
|
-
import type { SessionProgress } from "../../client.js";
|
|
4
|
+
import type { SessionProgress, SessionToolOutput } from "../../client.js";
|
|
5
5
|
import { ShiitakeError } from "../../errors.js";
|
|
6
6
|
import type { SqliteOwner } from "../contracts.js";
|
|
7
7
|
import type { AcceptedCommand } from "./session-store.js";
|
|
8
|
+
export declare function readProcessToolOutputForWork(owner: SqliteOwner, sessionId: string, workId: string): readonly SessionToolOutput[];
|
|
9
|
+
/** Run-scoped, replaceable tool snapshots: never stored in Woodstock. */
|
|
10
|
+
export declare function openToolProgress(owner: SqliteOwner, accepted: AcceptedCommand): Effect.Effect<{
|
|
11
|
+
started: (event: Extract<MuleEvent, {
|
|
12
|
+
type: "tool_execution_start";
|
|
13
|
+
}>) => Effect.Effect<void, never, never>;
|
|
14
|
+
update: (event: Extract<MuleEvent, {
|
|
15
|
+
type: "tool_execution_update";
|
|
16
|
+
}>) => Effect.Effect<void, never, never>;
|
|
17
|
+
terminal: (event: Extract<MuleEvent, {
|
|
18
|
+
type: "tool_execution_end";
|
|
19
|
+
}>) => Effect.Effect<void, never, never>;
|
|
20
|
+
}, never, Scope.Scope>;
|
|
8
21
|
/** Read the current process-local progress value for one addressed Session. */
|
|
9
22
|
export declare function readProcessProgress(owner: SqliteOwner, sessionId: string): SessionProgress | null;
|
|
10
23
|
/** Read progress only when it belongs to the current durable work head. */
|
|
@@ -1,7 +1,65 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import * as Result from "effect/Result";
|
|
2
4
|
import { ShiitakeError } from "../../errors.js";
|
|
3
5
|
import { publishOwnerObservation } from "../observation-kernel.js";
|
|
4
6
|
const progressByOwner = new WeakMap();
|
|
7
|
+
export function readProcessToolOutputForWork(owner, sessionId, workId) {
|
|
8
|
+
const current = progressByOwner.get(owner)?.get(sessionId);
|
|
9
|
+
return current?.workId === workId
|
|
10
|
+
? [...current.slots.values()].filter((value) => value !== null)
|
|
11
|
+
: [];
|
|
12
|
+
}
|
|
13
|
+
const decodeOutputDetails = Schema.decodeUnknownResult(Schema.Struct({
|
|
14
|
+
droppedBytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
15
|
+
totalBytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
16
|
+
}), { onExcessProperty: "error" });
|
|
17
|
+
/** Run-scoped, replaceable tool snapshots: never stored in Woodstock. */
|
|
18
|
+
export function openToolProgress(owner, accepted) {
|
|
19
|
+
return Effect.gen(function* () {
|
|
20
|
+
const current = yield* openProcessProgress(owner, accepted);
|
|
21
|
+
const publish = () => {
|
|
22
|
+
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
started: (event) => Effect.sync(() => {
|
|
26
|
+
if (current.slots.size < 4 &&
|
|
27
|
+
!current.slots.has(event.toolCall.value))
|
|
28
|
+
current.slots.set(event.toolCall.value, null);
|
|
29
|
+
}),
|
|
30
|
+
update: (event) => Effect.sync(() => {
|
|
31
|
+
const previous = current.slots.get(event.toolCall.value);
|
|
32
|
+
if (previous === undefined)
|
|
33
|
+
return;
|
|
34
|
+
const details = decodeOutputDetails(event.result.data);
|
|
35
|
+
// Other tool-specific update contracts do not represent output tails.
|
|
36
|
+
if (Result.isFailure(details))
|
|
37
|
+
return;
|
|
38
|
+
const tail = event.result.content
|
|
39
|
+
.filter((part) => part.type === "text")
|
|
40
|
+
.map((part) => part.text)
|
|
41
|
+
.join("");
|
|
42
|
+
if (new TextEncoder().encode(tail).byteLength > 8192 ||
|
|
43
|
+
tail.split("\n").length > 200 ||
|
|
44
|
+
details.success.droppedBytes > details.success.totalBytes)
|
|
45
|
+
return;
|
|
46
|
+
current.slots.set(event.toolCall.value, {
|
|
47
|
+
// Slots follow Mule execution identity; clients join the provider's
|
|
48
|
+
// call ID carried by assistant calls and durable tool results.
|
|
49
|
+
toolCallId: event.call.id,
|
|
50
|
+
revision: (previous?.revision ?? 0) + 1,
|
|
51
|
+
tail,
|
|
52
|
+
...details.success,
|
|
53
|
+
});
|
|
54
|
+
publish();
|
|
55
|
+
}),
|
|
56
|
+
terminal: (event) => Effect.sync(() => {
|
|
57
|
+
if (current.slots.delete(event.toolCall.value))
|
|
58
|
+
publish();
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
5
63
|
function progressFor(owner) {
|
|
6
64
|
const existing = progressByOwner.get(owner);
|
|
7
65
|
if (existing !== undefined)
|
|
@@ -10,6 +68,29 @@ function progressFor(owner) {
|
|
|
10
68
|
progressByOwner.set(owner, created);
|
|
11
69
|
return created;
|
|
12
70
|
}
|
|
71
|
+
/** One run-scoped owner for message progress and tool tails. */
|
|
72
|
+
function openProcessProgress(owner, accepted) {
|
|
73
|
+
return Effect.gen(function* () {
|
|
74
|
+
const sessions = progressFor(owner);
|
|
75
|
+
const existing = sessions.get(accepted.sessionId);
|
|
76
|
+
if (existing?.workId === accepted.commandId)
|
|
77
|
+
return existing;
|
|
78
|
+
const current = {
|
|
79
|
+
workId: accepted.commandId,
|
|
80
|
+
value: null,
|
|
81
|
+
slots: new Map(),
|
|
82
|
+
};
|
|
83
|
+
sessions.set(accepted.sessionId, current);
|
|
84
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
85
|
+
if (sessions.get(accepted.sessionId) !== current)
|
|
86
|
+
return;
|
|
87
|
+
sessions.delete(accepted.sessionId);
|
|
88
|
+
if (current.value !== null || current.slots.size > 0)
|
|
89
|
+
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
90
|
+
}));
|
|
91
|
+
return current;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
13
94
|
function rejected(message) {
|
|
14
95
|
return new ShiitakeError("runtime", "rejected", message);
|
|
15
96
|
}
|
|
@@ -41,9 +122,12 @@ function textFor(event) {
|
|
|
41
122
|
*/
|
|
42
123
|
export function openProgress(owner, accepted, complete) {
|
|
43
124
|
return Effect.gen(function* () {
|
|
125
|
+
const current = yield* openProcessProgress(owner, accepted);
|
|
44
126
|
let started;
|
|
45
127
|
const clear = Effect.sync(() => {
|
|
46
|
-
|
|
128
|
+
const hadProgress = current.value !== null;
|
|
129
|
+
current.value = null;
|
|
130
|
+
if (hadProgress)
|
|
47
131
|
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
48
132
|
started = undefined;
|
|
49
133
|
});
|
|
@@ -51,7 +135,7 @@ export function openProgress(owner, accepted, complete) {
|
|
|
51
135
|
return Object.freeze({
|
|
52
136
|
started: (event) => Effect.sync(() => {
|
|
53
137
|
started = event;
|
|
54
|
-
|
|
138
|
+
current.value = null;
|
|
55
139
|
}),
|
|
56
140
|
update: (event) => Effect.suspend(() => {
|
|
57
141
|
const messageStart = started;
|
|
@@ -61,25 +145,19 @@ export function openProgress(owner, accepted, complete) {
|
|
|
61
145
|
messageStart.turn.value !== event.turn.value ||
|
|
62
146
|
messageStart.message.value !== event.message.value)
|
|
63
147
|
return Effect.fail(rejected("Mule progress identity conflicts"));
|
|
64
|
-
const previous =
|
|
148
|
+
const previous = current.value;
|
|
65
149
|
const content = textFor(event);
|
|
66
|
-
if (previous?.
|
|
67
|
-
previous.
|
|
68
|
-
previous.value.thinking === content.thinking)
|
|
150
|
+
if (previous?.text === content.text &&
|
|
151
|
+
previous.thinking === content.thinking)
|
|
69
152
|
return Effect.void;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
thinking: content.thinking,
|
|
79
|
-
textTruncated: false,
|
|
80
|
-
thinkingTruncated: false,
|
|
81
|
-
}),
|
|
82
|
-
}));
|
|
153
|
+
current.value = Object.freeze({
|
|
154
|
+
messageId: event.message.value,
|
|
155
|
+
revision: (previous?.revision ?? 0) + 1,
|
|
156
|
+
text: content.text,
|
|
157
|
+
thinking: content.thinking,
|
|
158
|
+
textTruncated: false,
|
|
159
|
+
thinkingTruncated: false,
|
|
160
|
+
});
|
|
83
161
|
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
84
162
|
return Effect.void;
|
|
85
163
|
}),
|
|
@@ -10,7 +10,7 @@ export function recoverAssistantTurn(rows) {
|
|
|
10
10
|
// A later request was already dispatched. Resend that request from the
|
|
11
11
|
// completed transcript, not the previous assistant turn.
|
|
12
12
|
if (after.some(({ entry }) => entry.kind === "turn_started" ||
|
|
13
|
-
(entry.kind === "compaction" && entry.resumesRun
|
|
13
|
+
(entry.kind === "compaction" && entry.data.resumesRun) ||
|
|
14
14
|
(entry.kind === "live_retry" &&
|
|
15
15
|
entry.event.type === "auto_retry_start")))
|
|
16
16
|
return undefined;
|
|
@@ -28,7 +28,9 @@ export function recoverAssistantTurn(rows) {
|
|
|
28
28
|
{
|
|
29
29
|
call,
|
|
30
30
|
state: "completed",
|
|
31
|
-
result: outcome.
|
|
31
|
+
result: outcome.data === undefined
|
|
32
|
+
? outcome.result
|
|
33
|
+
: { ...outcome.result, data: outcome.data },
|
|
32
34
|
message: toolResults.find((result) => result.toolCallId === call.id),
|
|
33
35
|
},
|
|
34
36
|
];
|
|
@@ -57,7 +59,7 @@ export function contextMessageIds(path, activeWorkId) {
|
|
|
57
59
|
if (terminalWork.has(entry.workId) || entry.workId === activeWorkId)
|
|
58
60
|
messages.add(row.id);
|
|
59
61
|
}
|
|
60
|
-
if ((entry.kind === "compaction" && entry.resumesRun
|
|
62
|
+
if ((entry.kind === "compaction" && entry.data.resumesRun) ||
|
|
61
63
|
(entry.kind === "live_retry" && entry.event.type === "auto_retry_start")) {
|
|
62
64
|
const failed = lastWorkAssistant.get(entry.workId);
|
|
63
65
|
if (failed !== undefined)
|
|
@@ -74,7 +76,7 @@ export function abnormalTerminal(rows) {
|
|
|
74
76
|
return undefined;
|
|
75
77
|
if (rows.some(({ sequence, entry }) => sequence > terminal.sequence &&
|
|
76
78
|
(entry.kind === "run_started" ||
|
|
77
|
-
(entry.kind === "compaction" && entry.resumesRun
|
|
79
|
+
(entry.kind === "compaction" && entry.data.resumesRun))))
|
|
78
80
|
return undefined;
|
|
79
81
|
if (terminal.entry.outcome === "cancelled")
|
|
80
82
|
return "cancelled";
|
|
@@ -4,7 +4,7 @@ import type { SqliteOwner } from "../contracts.js";
|
|
|
4
4
|
export declare function prepareSessionAttention(owner: SqliteOwner): void;
|
|
5
5
|
/** Read the complete bounded attention projection inside the owner's transaction. */
|
|
6
6
|
export declare function readSessionAttention(owner: SqliteOwner, sessionId: string): readonly SessionAttention[];
|
|
7
|
-
/** Whether any owner
|
|
7
|
+
/** Whether any owner requests inbox attention; drawer-only items stay visible. */
|
|
8
8
|
export declare function hasSessionAttention(owner: SqliteOwner, sessionId: string): boolean;
|
|
9
9
|
/** Upsert only the calling extension's item, in the calling transaction. */
|
|
10
10
|
export declare function setSessionAttention(owner: SqliteOwner, sessionId: string, extensionId: string, item: AttentionItem): void;
|