@frockbot/plugin-shell 0.3.15 → 0.3.17
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 +32 -32
- package/src/agent.test.ts +85 -2
- package/src/agent.ts +87 -20
- package/src/backend-applets.ts +2 -2
- package/src/backend-completion.test.ts +22 -5
- package/src/backend-composition.ts +20 -0
- package/src/backend-configuration.test.ts +7 -5
- package/src/backend-debug.test.ts +49 -0
- package/src/backend-flock.ts +119 -10
- package/src/backend-recovery-integration.test.ts +17 -3
- package/src/backend-runner.ts +1 -0
- package/src/backend.ts +133 -12
- package/src/client/FrockBotApp.vue +6 -1
- package/src/client/index.test.ts +61 -0
- package/src/client/index.ts +43 -10
- package/src/client/mobile-safe-area.test.ts +25 -0
- package/src/client/styles.css +28 -0
- package/src/compaction.test.ts +26 -0
- package/src/compaction.ts +46 -14
- package/src/debug-protocol.ts +4 -3
- package/src/history.ts +8 -2
- package/src/run-failure-copy.test.ts +11 -0
- package/src/run-failure-copy.ts +5 -1
- package/src/run-protocol.test.ts +73 -4
- package/src/run-protocol.ts +118 -12
- package/src/shared.ts +3 -0
package/src/run-protocol.ts
CHANGED
|
@@ -165,6 +165,11 @@ export type ClientRunEventV1 =
|
|
|
165
165
|
type: "wake/parent";
|
|
166
166
|
message: string;
|
|
167
167
|
}
|
|
168
|
+
| {
|
|
169
|
+
type: "computer/sync";
|
|
170
|
+
status: "degraded" | "unavailable" | "refused" | "skipped";
|
|
171
|
+
message: string;
|
|
172
|
+
}
|
|
168
173
|
/**
|
|
169
174
|
* A subagent this Turn dispatched (ADR 0017). The child's Session never
|
|
170
175
|
* enters the visible transcript, so this chip is the whole of what the
|
|
@@ -212,12 +217,12 @@ export interface ClientRunRecoveryV1 {
|
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
/**
|
|
215
|
-
* The run projection. Version 2
|
|
216
|
-
* `events
|
|
217
|
-
*
|
|
220
|
+
* The run projection. Version 2 added structured `send/to-user` and
|
|
221
|
+
* `wake/parent` events; version 3 adds the bounded `via` marker for agent and
|
|
222
|
+
* Voice Turns without exposing the internal origin record.
|
|
218
223
|
*/
|
|
219
224
|
export interface ClientRunV1 {
|
|
220
|
-
schemaVersion: 1 | 2;
|
|
225
|
+
schemaVersion: 1 | 2 | 3;
|
|
221
226
|
runId: string;
|
|
222
227
|
admittedAt: string;
|
|
223
228
|
input: string;
|
|
@@ -241,6 +246,10 @@ export interface ClientRunV1 {
|
|
|
241
246
|
partialText?: string;
|
|
242
247
|
outcome?: ClientRunOutcomeV1;
|
|
243
248
|
recovery?: ClientRunRecoveryV1;
|
|
249
|
+
/** Where an agent-lane question entered this Bot's transcript. */
|
|
250
|
+
via?:
|
|
251
|
+
| { kind: "bot"; name: string; botId: string }
|
|
252
|
+
| { kind: "voice"; name: "Voice" };
|
|
244
253
|
}
|
|
245
254
|
|
|
246
255
|
export interface ClientRunPageV1 {
|
|
@@ -624,6 +633,7 @@ function projectionUnits(
|
|
|
624
633
|
const units: ProjectionUnitV1[] = [];
|
|
625
634
|
const byOccurrence = new Map<string, ProjectionUnitV1>();
|
|
626
635
|
let callCount = 0;
|
|
636
|
+
let projectedIncompleteSync = false;
|
|
627
637
|
for (const event of events) {
|
|
628
638
|
if (event.type === "tool/call") {
|
|
629
639
|
if (byOccurrence.has(event.occurrenceId)) {
|
|
@@ -694,6 +704,22 @@ function projectionUnits(
|
|
|
694
704
|
],
|
|
695
705
|
droppable: true,
|
|
696
706
|
});
|
|
707
|
+
} else if (
|
|
708
|
+
event.type === "computer/sync" &&
|
|
709
|
+
event.status !== "ok" &&
|
|
710
|
+
!projectedIncompleteSync
|
|
711
|
+
) {
|
|
712
|
+
projectedIncompleteSync = true;
|
|
713
|
+
units.push({
|
|
714
|
+
events: [
|
|
715
|
+
{
|
|
716
|
+
type: "computer/sync",
|
|
717
|
+
status: event.status,
|
|
718
|
+
message: computerSyncCopyV1(event),
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
droppable: true,
|
|
722
|
+
});
|
|
697
723
|
} else if (event.type === "task/dispatched") {
|
|
698
724
|
units.push({
|
|
699
725
|
events: [
|
|
@@ -736,6 +762,24 @@ function projectionUnits(
|
|
|
736
762
|
return units;
|
|
737
763
|
}
|
|
738
764
|
|
|
765
|
+
function computerSyncCopyV1(
|
|
766
|
+
event: Extract<SessionEvent, { type: "computer/sync" }>,
|
|
767
|
+
): string {
|
|
768
|
+
if (event.status === "degraded") {
|
|
769
|
+
return (
|
|
770
|
+
event.detail.trim() ||
|
|
771
|
+
"Some Workspace files did not sync during this turn."
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
if (event.status === "unavailable") {
|
|
775
|
+
return "Workspace files could not sync during this turn. Sync will retry the next time the Computer is used.";
|
|
776
|
+
}
|
|
777
|
+
if (event.status === "refused") {
|
|
778
|
+
return "Workspace sync could not run for this turn.";
|
|
779
|
+
}
|
|
780
|
+
return "Workspace sync was skipped for this turn.";
|
|
781
|
+
}
|
|
782
|
+
|
|
739
783
|
function visibleEvents(
|
|
740
784
|
events: readonly SessionEvent[],
|
|
741
785
|
status: ClientRunStatusV1,
|
|
@@ -876,10 +920,20 @@ export function projectClientRunV1(run: StoredRun): ClientRunV1 {
|
|
|
876
920
|
message: RESUMABLE_RUN_MESSAGE_V1,
|
|
877
921
|
} satisfies ClientRunRecoveryV1)
|
|
878
922
|
: undefined;
|
|
923
|
+
const origin = run.admission?.origin;
|
|
924
|
+
const via =
|
|
925
|
+
origin?.kind === "bot"
|
|
926
|
+
? {
|
|
927
|
+
kind: "bot" as const,
|
|
928
|
+
name: truncateWireString(origin.fromBotName, 100),
|
|
929
|
+
botId: truncate(origin.fromBotId, 128),
|
|
930
|
+
}
|
|
931
|
+
: origin?.kind === "voice"
|
|
932
|
+
? { kind: "voice" as const, name: "Voice" as const }
|
|
933
|
+
: undefined;
|
|
879
934
|
return {
|
|
880
|
-
// Version
|
|
881
|
-
|
|
882
|
-
schemaVersion: 2,
|
|
935
|
+
// Version 3 adds the origin marker for an agent-lane message.
|
|
936
|
+
schemaVersion: 3,
|
|
883
937
|
runId: truncate(run.runId, MAX_RUN_ID_LENGTH),
|
|
884
938
|
admittedAt: truncate(run.acceptedAt, MAX_TIMESTAMP_LENGTH),
|
|
885
939
|
input: truncateWireString(run.input, MAX_INPUT_BYTES),
|
|
@@ -896,6 +950,7 @@ export function projectClientRunV1(run: StoredRun): ClientRunV1 {
|
|
|
896
950
|
: {}),
|
|
897
951
|
...(outcome ? { outcome } : {}),
|
|
898
952
|
...(recovery ? { recovery } : {}),
|
|
953
|
+
...(via ? { via } : {}),
|
|
899
954
|
};
|
|
900
955
|
}
|
|
901
956
|
|
|
@@ -921,7 +976,8 @@ function lookupState(
|
|
|
921
976
|
export function isVisibleRunV1(run: {
|
|
922
977
|
admission?: { turnType?: string };
|
|
923
978
|
}): boolean {
|
|
924
|
-
|
|
979
|
+
const type = run.admission?.turnType ?? "chat";
|
|
980
|
+
return type === "chat" || type === "agent";
|
|
925
981
|
}
|
|
926
982
|
|
|
927
983
|
export function projectClientRunLookupV1(
|
|
@@ -1248,6 +1304,27 @@ function decodeEvent(value: unknown): ClientRunEventV1 {
|
|
|
1248
1304
|
),
|
|
1249
1305
|
};
|
|
1250
1306
|
}
|
|
1307
|
+
if (event.type === "computer/sync") {
|
|
1308
|
+
exactKeys(event, ["type", "status", "message"], "run event");
|
|
1309
|
+
if (
|
|
1310
|
+
event.status !== "degraded" &&
|
|
1311
|
+
event.status !== "unavailable" &&
|
|
1312
|
+
event.status !== "refused" &&
|
|
1313
|
+
event.status !== "skipped"
|
|
1314
|
+
) {
|
|
1315
|
+
throw new Error("run event.status is invalid");
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
type: "computer/sync",
|
|
1319
|
+
status: event.status,
|
|
1320
|
+
message: wireString(
|
|
1321
|
+
event,
|
|
1322
|
+
"message",
|
|
1323
|
+
MAX_EVENT_CONTENT_BYTES,
|
|
1324
|
+
"run event",
|
|
1325
|
+
),
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1251
1328
|
if (event.type === "task/dispatched") {
|
|
1252
1329
|
exactKeys(
|
|
1253
1330
|
event,
|
|
@@ -1299,12 +1376,13 @@ function decodeEvents(values: unknown[]): ClientTurnEvent[] {
|
|
|
1299
1376
|
const callIds = new Set<string>();
|
|
1300
1377
|
while (index < events.length) {
|
|
1301
1378
|
const call = events[index];
|
|
1302
|
-
//
|
|
1303
|
-
// call/result walk steps straight over them.
|
|
1379
|
+
// Sends, hand-offs, task chips, and sync notices stand alone: they pair
|
|
1380
|
+
// with nothing, so the call/result walk steps straight over them.
|
|
1304
1381
|
if (
|
|
1305
1382
|
call?.type === "send/to-user" ||
|
|
1306
1383
|
call?.type === "wake/parent" ||
|
|
1307
|
-
call?.type === "task/dispatched"
|
|
1384
|
+
call?.type === "task/dispatched" ||
|
|
1385
|
+
call?.type === "computer/sync"
|
|
1308
1386
|
) {
|
|
1309
1387
|
index += 1;
|
|
1310
1388
|
continue;
|
|
@@ -1429,14 +1507,41 @@ function decodeRun(value: unknown): ClientRun {
|
|
|
1429
1507
|
"partialText",
|
|
1430
1508
|
"outcome",
|
|
1431
1509
|
"recovery",
|
|
1510
|
+
"via",
|
|
1432
1511
|
],
|
|
1433
1512
|
"run",
|
|
1434
1513
|
);
|
|
1435
1514
|
// 1 and 2 differ only by the event types a version 2 body may carry, and a
|
|
1436
1515
|
// version 1 body carries a subset of them, so one walk decodes both.
|
|
1437
|
-
if (
|
|
1516
|
+
if (
|
|
1517
|
+
run.schemaVersion !== 1 &&
|
|
1518
|
+
run.schemaVersion !== 2 &&
|
|
1519
|
+
run.schemaVersion !== 3
|
|
1520
|
+
) {
|
|
1438
1521
|
throw new Error("run.schemaVersion is invalid");
|
|
1439
1522
|
}
|
|
1523
|
+
let via: ClientRunV1["via"];
|
|
1524
|
+
if (run.via !== undefined) {
|
|
1525
|
+
if (run.schemaVersion !== 3) {
|
|
1526
|
+
throw new Error("run.via requires schemaVersion 3");
|
|
1527
|
+
}
|
|
1528
|
+
const candidate = record(run.via, "run.via");
|
|
1529
|
+
if (candidate.kind === "bot") {
|
|
1530
|
+
exactKeys(candidate, ["kind", "name", "botId"], "run.via");
|
|
1531
|
+
via = {
|
|
1532
|
+
kind: "bot",
|
|
1533
|
+
name: wireString(candidate, "name", 100, "run.via"),
|
|
1534
|
+
botId: string(candidate, "botId", 128, "run.via"),
|
|
1535
|
+
};
|
|
1536
|
+
} else if (candidate.kind === "voice") {
|
|
1537
|
+
exactKeys(candidate, ["kind", "name"], "run.via");
|
|
1538
|
+
if (candidate.name !== "Voice")
|
|
1539
|
+
throw new Error("run.via.name is invalid");
|
|
1540
|
+
via = { kind: "voice", name: "Voice" };
|
|
1541
|
+
} else {
|
|
1542
|
+
throw new Error("run.via.kind is invalid");
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1440
1545
|
let runId: string;
|
|
1441
1546
|
try {
|
|
1442
1547
|
runId = decodeRunIdV1(string(run, "runId", MAX_RUN_ID_LENGTH, "run"));
|
|
@@ -1507,6 +1612,7 @@ function decodeRun(value: unknown): ClientRun {
|
|
|
1507
1612
|
}
|
|
1508
1613
|
: {}),
|
|
1509
1614
|
...(recovery ? { failure: recovery.message, recovery } : {}),
|
|
1615
|
+
...(via ? { via } : {}),
|
|
1510
1616
|
};
|
|
1511
1617
|
}
|
|
1512
1618
|
|
package/src/shared.ts
CHANGED
|
@@ -98,6 +98,9 @@ export interface WebChatMessage {
|
|
|
98
98
|
| "error"
|
|
99
99
|
| "interrupted"
|
|
100
100
|
| "reconciliation-required";
|
|
101
|
+
via?:
|
|
102
|
+
| { kind: "bot"; name: string; botId: string }
|
|
103
|
+
| { kind: "voice"; name: "Voice" };
|
|
101
104
|
/**
|
|
102
105
|
* True while this line's Turn is admitted but has not started, because the
|
|
103
106
|
* User sent it while the Bot was still on the previous one. The thread
|