@frockbot/plugin-authoring 0.3.5 → 0.3.6
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/frockbot.json +1 -1
- package/package.json +2 -2
- package/src/agent.test.ts +50 -0
- package/src/agent.ts +84 -17
- package/src/records.ts +24 -7
package/frockbot.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-authoring",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
24
|
+
"@frockbot/kernel-contracts": "0.3.6",
|
|
25
25
|
"cordis": "4.0.0-rc.8"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
package/src/agent.test.ts
CHANGED
|
@@ -172,6 +172,56 @@ describe("the package_author tool", () => {
|
|
|
172
172
|
await dispose();
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
test("every refusal and every throw closes the intent with a failure event", async () => {
|
|
176
|
+
// F12: `package/author-intent` was written, the host refused, and the run
|
|
177
|
+
// reached `turn/end` with no `package/authored` and no failure event of
|
|
178
|
+
// any kind — the session log said an effect was intended and never said
|
|
179
|
+
// how it ended (user `packages-4`, Bot `maker-2351d56c`, run `e015c05e`).
|
|
180
|
+
for (const scenario of [
|
|
181
|
+
{
|
|
182
|
+
host: stubHost({
|
|
183
|
+
status: "refused" as const,
|
|
184
|
+
reason: "the Package bundler could not be reached",
|
|
185
|
+
failureId: "authoring-failure-2",
|
|
186
|
+
}),
|
|
187
|
+
expected: {
|
|
188
|
+
effect: "author",
|
|
189
|
+
reason: "the Package bundler could not be reached",
|
|
190
|
+
failureId: "authoring-failure-2",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
// An unmodelled throw escaped `execute` the same way.
|
|
195
|
+
host: {
|
|
196
|
+
...stubHost({
|
|
197
|
+
status: "refused" as const,
|
|
198
|
+
reason: "unused",
|
|
199
|
+
failureId: "unused",
|
|
200
|
+
}),
|
|
201
|
+
author: () => Promise.reject(new Error("storage is unavailable")),
|
|
202
|
+
},
|
|
203
|
+
expected: { effect: "author", reason: "storage is unavailable" },
|
|
204
|
+
},
|
|
205
|
+
]) {
|
|
206
|
+
const { session, sessions, dispose } = await openSession();
|
|
207
|
+
const tool = createPackageAuthorTool(scenario.host as never, sessions);
|
|
208
|
+
|
|
209
|
+
const result = await tool.execute(INPUT, CONTEXT);
|
|
210
|
+
|
|
211
|
+
expect(result.isError).toBe(true);
|
|
212
|
+
const types = session.events.map((event) => event.type);
|
|
213
|
+
expect(types).toContain("package/author-intent");
|
|
214
|
+
expect(types).not.toContain("package/authored");
|
|
215
|
+
expect(types.at(-1)).toBe("package/effect-failed");
|
|
216
|
+
expect(session.events.at(-1)).toMatchObject({
|
|
217
|
+
...scenario.expected,
|
|
218
|
+
turn: 4,
|
|
219
|
+
step: 2,
|
|
220
|
+
});
|
|
221
|
+
await dispose();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
175
225
|
test("a refusal leaves the intent recorded and no authored event", async () => {
|
|
176
226
|
const { session, sessions, dispose } = await openSession();
|
|
177
227
|
const tool = createPackageAuthorTool(
|
package/src/agent.ts
CHANGED
|
@@ -98,6 +98,41 @@ function refusalText(
|
|
|
98
98
|
].join(" ");
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Close an open `package/*-intent` with the failure that ended it, then answer
|
|
103
|
+
* the model. The pair is the proof the effect happened at all, so the failure
|
|
104
|
+
* event is flushed before the tool result is returned; a failure to record the
|
|
105
|
+
* failure is not allowed to hide the original one.
|
|
106
|
+
*/
|
|
107
|
+
async function closeWithFailure(
|
|
108
|
+
session: Session,
|
|
109
|
+
position: AuthoringTurnPositionV1,
|
|
110
|
+
failure: {
|
|
111
|
+
effectId: string;
|
|
112
|
+
effect: "author" | "undo" | "catalog-change";
|
|
113
|
+
reason: string;
|
|
114
|
+
failureId?: string;
|
|
115
|
+
content: string;
|
|
116
|
+
},
|
|
117
|
+
): Promise<{ content: string; isError: true }> {
|
|
118
|
+
try {
|
|
119
|
+
session.append({
|
|
120
|
+
type: "package/effect-failed",
|
|
121
|
+
...position,
|
|
122
|
+
effectId: failure.effectId,
|
|
123
|
+
effect: failure.effect,
|
|
124
|
+
reason: failure.reason,
|
|
125
|
+
...(failure.failureId === undefined
|
|
126
|
+
? {}
|
|
127
|
+
: { failureId: failure.failureId }),
|
|
128
|
+
});
|
|
129
|
+
await session.flush();
|
|
130
|
+
} catch {
|
|
131
|
+
// Deliberately swallowed: the model still gets the real reason.
|
|
132
|
+
}
|
|
133
|
+
return { content: failure.content, isError: true };
|
|
134
|
+
}
|
|
135
|
+
|
|
101
136
|
function authoredText(
|
|
102
137
|
outcome: Extract<AuthorPackageOutcomeV1, { status: "authored" }>,
|
|
103
138
|
): string {
|
|
@@ -181,15 +216,34 @@ export function createPackageAuthorTool(
|
|
|
181
216
|
});
|
|
182
217
|
await session.flush();
|
|
183
218
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
219
|
+
let outcome: AuthorPackageOutcomeV1;
|
|
220
|
+
try {
|
|
221
|
+
outcome = await host.author({
|
|
222
|
+
input: decoded,
|
|
223
|
+
sourceHash,
|
|
224
|
+
effectId,
|
|
225
|
+
sessionId: context.sessionId,
|
|
226
|
+
position,
|
|
227
|
+
});
|
|
228
|
+
} catch (error) {
|
|
229
|
+
// An unmodelled throw — a storage error, a quota RPC rejection, a
|
|
230
|
+
// manifest that will not decode — used to escape `execute` and leave
|
|
231
|
+
// the intent with no outcome of any kind (F12).
|
|
232
|
+
return closeWithFailure(session, position, {
|
|
233
|
+
effectId,
|
|
234
|
+
effect: "author",
|
|
235
|
+
reason: errorMessage(error),
|
|
236
|
+
content: `package_author failed: ${errorMessage(error)} Nothing was activated.`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
191
239
|
if (outcome.status === "refused") {
|
|
192
|
-
return
|
|
240
|
+
return closeWithFailure(session, position, {
|
|
241
|
+
effectId,
|
|
242
|
+
effect: "author",
|
|
243
|
+
reason: outcome.reason,
|
|
244
|
+
failureId: outcome.failureId,
|
|
245
|
+
content: refusalText(outcome),
|
|
246
|
+
});
|
|
193
247
|
}
|
|
194
248
|
session.append({
|
|
195
249
|
type: "package/authored",
|
|
@@ -255,17 +309,30 @@ export function createPackageUndoTool(
|
|
|
255
309
|
: {}),
|
|
256
310
|
});
|
|
257
311
|
await session.flush();
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
312
|
+
let outcome: PackageUndoOutcomeV1;
|
|
313
|
+
try {
|
|
314
|
+
outcome = await host.undo({
|
|
315
|
+
input: decoded,
|
|
316
|
+
effectId,
|
|
317
|
+
sessionId: context.sessionId,
|
|
318
|
+
position,
|
|
319
|
+
});
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return closeWithFailure(session, position, {
|
|
322
|
+
effectId,
|
|
323
|
+
effect: "undo",
|
|
324
|
+
reason: errorMessage(error),
|
|
325
|
+
content: `package_undo failed: ${errorMessage(error)} No connection action was undone.`,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
264
328
|
if (outcome.status === "refused") {
|
|
265
|
-
return {
|
|
329
|
+
return closeWithFailure(session, position, {
|
|
330
|
+
effectId,
|
|
331
|
+
effect: "undo",
|
|
332
|
+
reason: outcome.reason,
|
|
333
|
+
failureId: outcome.failureId,
|
|
266
334
|
content: `Package undo was refused: ${outcome.reason} A durable failure record "${outcome.failureId}" was written. No connection action was undone.`,
|
|
267
|
-
|
|
268
|
-
};
|
|
335
|
+
});
|
|
269
336
|
}
|
|
270
337
|
session.append({
|
|
271
338
|
type: "package/undo-recorded",
|
package/src/records.ts
CHANGED
|
@@ -190,6 +190,20 @@ export type AuthoringEffectOutcomeV1 =
|
|
|
190
190
|
effectId: string;
|
|
191
191
|
failureId: string;
|
|
192
192
|
reason: string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The bundler was never reached — unreachable, or past its deadline before
|
|
196
|
+
* it answered. Nothing ran, so nothing is uncertain: the effect is as good
|
|
197
|
+
* as unstarted and an identical retry may proceed. Without this an outage
|
|
198
|
+
* poisoned that exact `effectId` (run + Package + source hash) for the rest
|
|
199
|
+
* of the run, so retrying the same source could never succeed (F11).
|
|
200
|
+
*/
|
|
201
|
+
| {
|
|
202
|
+
schemaVersion: 1;
|
|
203
|
+
status: "unreachable";
|
|
204
|
+
effectId: string;
|
|
205
|
+
failureId: string;
|
|
206
|
+
reason: string;
|
|
193
207
|
};
|
|
194
208
|
|
|
195
209
|
export type AuthoringEffectClassificationV1 =
|
|
@@ -225,13 +239,16 @@ export function classifyAuthoringEffectV1(input: {
|
|
|
225
239
|
"an authoring outcome is recorded for an effect with no recorded intent",
|
|
226
240
|
};
|
|
227
241
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
if (input.outcome.status === "bundled") {
|
|
243
|
+
return { kind: "settled", outcome: input.outcome };
|
|
244
|
+
}
|
|
245
|
+
// Nothing ran, so the effect may run: `fresh`, not `unknown`.
|
|
246
|
+
if (input.outcome.status === "unreachable") return { kind: "fresh" };
|
|
247
|
+
return {
|
|
248
|
+
kind: "failed",
|
|
249
|
+
failureId: input.outcome.failureId,
|
|
250
|
+
reason: input.outcome.reason,
|
|
251
|
+
};
|
|
235
252
|
}
|
|
236
253
|
if (!input.intent) return { kind: "fresh" };
|
|
237
254
|
return {
|