@gajae-code/coding-agent 0.12.10 → 0.12.11
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 +9 -2
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +12 -2
- package/dist/types/session/session-manager.d.ts +17 -0
- package/dist/types/session/session-storage.d.ts +1 -0
- package/dist/types/tools/computer.d.ts +35 -2
- package/dist/types/tools/todo-write.d.ts +2 -0
- package/package.json +8 -8
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/prompts/tools/computer.md +6 -1
- package/src/sdk/bus/chat-daemon-control.ts +5 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
- package/src/session/agent-session.ts +111 -21
- package/src/session/internal/managed-session-storage.ts +244 -246
- package/src/session/session-manager.ts +140 -6
- package/src/session/session-storage.ts +3 -0
- package/src/tools/computer.ts +322 -45
- package/src/tools/todo-write.ts +29 -11
package/src/tools/computer.ts
CHANGED
|
@@ -99,7 +99,7 @@ export interface ComputerScreenshotDetails {
|
|
|
99
99
|
originX?: number;
|
|
100
100
|
originY?: number;
|
|
101
101
|
displayEpoch?: number;
|
|
102
|
-
captureId?:
|
|
102
|
+
captureId?: number;
|
|
103
103
|
pngBytes?: number;
|
|
104
104
|
path?: string;
|
|
105
105
|
}
|
|
@@ -119,12 +119,50 @@ export interface ComputerToolDetails {
|
|
|
119
119
|
keys?: string[];
|
|
120
120
|
ms?: number;
|
|
121
121
|
screenshot?: ComputerScreenshotDetails;
|
|
122
|
+
primaryError?: { code: string; message: string };
|
|
122
123
|
supervisor?: string;
|
|
123
124
|
steps?: ComputerToolDetails[];
|
|
124
125
|
meta?: OutputMeta;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
type NativeBatchAction = {
|
|
129
|
+
action: "screenshot" | "click" | "double_click" | "move" | "drag" | "scroll" | "type" | "keypress" | "wait";
|
|
130
|
+
x?: number;
|
|
131
|
+
y?: number;
|
|
132
|
+
toX?: number;
|
|
133
|
+
toY?: number;
|
|
134
|
+
scrollX?: number;
|
|
135
|
+
scrollY?: number;
|
|
136
|
+
button?: string;
|
|
137
|
+
text?: string;
|
|
138
|
+
keys?: string[];
|
|
139
|
+
ms?: number;
|
|
140
|
+
timeoutMs?: number;
|
|
141
|
+
timeoutGroup?: number;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
type NativeBatchStepResult = {
|
|
145
|
+
index: number;
|
|
146
|
+
action: string;
|
|
147
|
+
screenshot?: NativeScreenshot;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
type NativeBatchResult = {
|
|
151
|
+
results: NativeBatchStepResult[];
|
|
152
|
+
failureCode?: string;
|
|
153
|
+
failureIndex?: number;
|
|
154
|
+
failureMessage?: string;
|
|
155
|
+
primaryFailureCode?: string;
|
|
156
|
+
primaryFailureMessage?: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
127
159
|
type NativeController = {
|
|
160
|
+
executeBatch?: (
|
|
161
|
+
expectedEpoch: number | undefined,
|
|
162
|
+
actions: NativeBatchAction[],
|
|
163
|
+
timeoutMs?: number,
|
|
164
|
+
signal?: AbortSignal,
|
|
165
|
+
) => Promise<NativeBatchResult> | NativeBatchResult;
|
|
128
166
|
screenshot?: () => Promise<NativeScreenshot> | NativeScreenshot;
|
|
129
167
|
click?: (expectedEpoch: number | undefined, x: number, y: number, button?: string) => void;
|
|
130
168
|
doubleClick?: (expectedEpoch: number | undefined, x: number, y: number, button?: string) => void;
|
|
@@ -145,7 +183,7 @@ type NativeScreenshot = {
|
|
|
145
183
|
originX?: number;
|
|
146
184
|
originY?: number;
|
|
147
185
|
displayEpoch?: number;
|
|
148
|
-
captureId?:
|
|
186
|
+
captureId?: number;
|
|
149
187
|
};
|
|
150
188
|
|
|
151
189
|
export type ComputerControllerFactory = () => NativeController;
|
|
@@ -183,7 +221,7 @@ const COMPUTER_INLINE_SCREENSHOT_PROVIDER_MAX_BYTES = 5 * 1024 * 1024;
|
|
|
183
221
|
const COMPUTER_INLINE_SCREENSHOT_JPEG_QUALITY = 70;
|
|
184
222
|
|
|
185
223
|
export function setComputerControllerFactoryForTests(factory: ComputerControllerFactory | undefined): void {
|
|
186
|
-
controllerFactory = factory
|
|
224
|
+
controllerFactory = factory ? () => withLegacyBatchAdapterForTests(factory()) : createNativeComputerController;
|
|
187
225
|
}
|
|
188
226
|
|
|
189
227
|
export function setComputerPlatformForTests(platform: NodeJS.Platform | undefined): void {
|
|
@@ -298,6 +336,10 @@ export class ComputerTool implements AgentTool<typeof computerSchema, ComputerTo
|
|
|
298
336
|
if (batchResult.failedStep) {
|
|
299
337
|
details.code = batchResult.failedStep.code;
|
|
300
338
|
details.message = batchResult.failedStep.message;
|
|
339
|
+
if (batchResult.primaryError) {
|
|
340
|
+
details.primaryError = batchResult.primaryError;
|
|
341
|
+
details.message += ` Primary failure: ${batchResult.primaryError.code}: ${batchResult.primaryError.message}`;
|
|
342
|
+
}
|
|
301
343
|
if (batchResult.screenshotSource !== undefined) {
|
|
302
344
|
await persistScreenshotFallback(batchResult.screenshotSource, details.screenshot, this.session);
|
|
303
345
|
}
|
|
@@ -320,6 +362,57 @@ export class ComputerTool implements AgentTool<typeof computerSchema, ComputerTo
|
|
|
320
362
|
.done()
|
|
321
363
|
: toolResult(details).text(details.message).done();
|
|
322
364
|
}
|
|
365
|
+
if (params.action !== "screenshot") {
|
|
366
|
+
const singleResult = await dispatchBatchComputerActions(
|
|
367
|
+
controller,
|
|
368
|
+
[params],
|
|
369
|
+
timeoutMs,
|
|
370
|
+
hotkey,
|
|
371
|
+
latestScreenshotContexts.get(this.session),
|
|
372
|
+
false,
|
|
373
|
+
Boolean(this.session.settings.get("computer.autoScreenshot")),
|
|
374
|
+
signal,
|
|
375
|
+
deadline,
|
|
376
|
+
);
|
|
377
|
+
if (singleResult.screenshot) {
|
|
378
|
+
details.screenshot = singleResult.screenshot;
|
|
379
|
+
rememberLatestScreenshot(this.session, singleResult.screenshot);
|
|
380
|
+
}
|
|
381
|
+
if (singleResult.failedStep) {
|
|
382
|
+
details.status = singleResult.failedStep.code === COMPUTER_DISABLED_CODE ? "disabled" : "error";
|
|
383
|
+
details.code = singleResult.failedStep.code;
|
|
384
|
+
details.message = singleResult.failedStep.message;
|
|
385
|
+
if (singleResult.primaryError) {
|
|
386
|
+
details.primaryError = singleResult.primaryError;
|
|
387
|
+
details.message += ` Primary failure: ${singleResult.primaryError.code}: ${singleResult.primaryError.message}`;
|
|
388
|
+
}
|
|
389
|
+
if (singleResult.screenshotSource !== undefined) {
|
|
390
|
+
await persistScreenshotFallback(singleResult.screenshotSource, details.screenshot, this.session);
|
|
391
|
+
}
|
|
392
|
+
await writeComputerAuditLog(this.session, details);
|
|
393
|
+
return {
|
|
394
|
+
...toolResult(details).text(`${details.code}: ${details.message}`).done(),
|
|
395
|
+
isError: true,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
details.status = "success";
|
|
399
|
+
details.message = describeComputerSuccess(details);
|
|
400
|
+
if (singleResult.screenshotSource !== undefined) {
|
|
401
|
+
await persistScreenshotFallback(singleResult.screenshotSource, details.screenshot, this.session);
|
|
402
|
+
details.message = describeComputerSuccess(details);
|
|
403
|
+
}
|
|
404
|
+
const image = await inlineImageContentFromNativeResult(
|
|
405
|
+
singleResult.screenshotSource,
|
|
406
|
+
details,
|
|
407
|
+
this.session,
|
|
408
|
+
);
|
|
409
|
+
await writeComputerAuditLog(this.session, details);
|
|
410
|
+
return image
|
|
411
|
+
? toolResult(details)
|
|
412
|
+
.content([{ type: "text", text: details.message }, image])
|
|
413
|
+
.done()
|
|
414
|
+
: toolResult(details).text(details.message).done();
|
|
415
|
+
}
|
|
323
416
|
let result = await dispatchComputerAction(
|
|
324
417
|
controller,
|
|
325
418
|
params,
|
|
@@ -509,6 +602,85 @@ async function runComputerOperation<T>(
|
|
|
509
602
|
}
|
|
510
603
|
}
|
|
511
604
|
|
|
605
|
+
function withLegacyBatchAdapterForTests(controller: NativeController): NativeController {
|
|
606
|
+
if (
|
|
607
|
+
controller.executeBatch ||
|
|
608
|
+
(!controller.click &&
|
|
609
|
+
!controller.doubleClick &&
|
|
610
|
+
!controller.move &&
|
|
611
|
+
!controller.drag &&
|
|
612
|
+
!controller.scroll &&
|
|
613
|
+
!controller.type &&
|
|
614
|
+
!controller.keypress &&
|
|
615
|
+
!controller.wait)
|
|
616
|
+
) {
|
|
617
|
+
return controller;
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
...controller,
|
|
621
|
+
executeBatch: async (expectedEpoch, actions, _timeoutMs, signal) => {
|
|
622
|
+
const results: NativeBatchStepResult[] = [];
|
|
623
|
+
for (const [index, action] of actions.entries()) {
|
|
624
|
+
throwIfAborted(signal);
|
|
625
|
+
switch (action.action) {
|
|
626
|
+
case "screenshot":
|
|
627
|
+
if (!controller.screenshot) missingNativeMethod("screenshot", "screenshot");
|
|
628
|
+
results.push({ index, action: "screenshot", screenshot: await controller.screenshot() });
|
|
629
|
+
break;
|
|
630
|
+
case "click":
|
|
631
|
+
if (!controller.click) missingNativeMethod("click", "click");
|
|
632
|
+
controller.click(expectedEpoch, action.x!, action.y!, action.button ?? "left");
|
|
633
|
+
results.push({ index, action: "click" });
|
|
634
|
+
break;
|
|
635
|
+
case "double_click":
|
|
636
|
+
if (!controller.doubleClick) missingNativeMethod("double_click", "doubleClick");
|
|
637
|
+
controller.doubleClick(expectedEpoch, action.x!, action.y!, action.button ?? "left");
|
|
638
|
+
results.push({ index, action: "double_click" });
|
|
639
|
+
break;
|
|
640
|
+
case "move":
|
|
641
|
+
if (!controller.move) missingNativeMethod("move", "move");
|
|
642
|
+
controller.move(expectedEpoch, action.x!, action.y!);
|
|
643
|
+
results.push({ index, action: "move" });
|
|
644
|
+
break;
|
|
645
|
+
case "drag":
|
|
646
|
+
if (!controller.drag) missingNativeMethod("drag", "drag");
|
|
647
|
+
controller.drag(
|
|
648
|
+
expectedEpoch,
|
|
649
|
+
action.x!,
|
|
650
|
+
action.y!,
|
|
651
|
+
action.toX!,
|
|
652
|
+
action.toY!,
|
|
653
|
+
action.button ?? "left",
|
|
654
|
+
);
|
|
655
|
+
results.push({ index, action: "drag" });
|
|
656
|
+
break;
|
|
657
|
+
case "scroll":
|
|
658
|
+
if (!controller.scroll) missingNativeMethod("scroll", "scroll");
|
|
659
|
+
controller.scroll(expectedEpoch, action.x!, action.y!, action.scrollX!, action.scrollY!);
|
|
660
|
+
results.push({ index, action: "scroll" });
|
|
661
|
+
break;
|
|
662
|
+
case "type":
|
|
663
|
+
if (!controller.type) missingNativeMethod("type", "type");
|
|
664
|
+
controller.type(undefined, action.text!);
|
|
665
|
+
results.push({ index, action: "type" });
|
|
666
|
+
break;
|
|
667
|
+
case "keypress":
|
|
668
|
+
if (!controller.keypress) missingNativeMethod("keypress", "keypress");
|
|
669
|
+
controller.keypress(undefined, action.keys!);
|
|
670
|
+
results.push({ index, action: "keypress" });
|
|
671
|
+
break;
|
|
672
|
+
case "wait":
|
|
673
|
+
if (!controller.wait) missingNativeMethod("wait", "wait");
|
|
674
|
+
controller.wait(undefined, action.ms!);
|
|
675
|
+
results.push({ index, action: "wait" });
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return { results };
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
512
684
|
function dispatchComputerAction(
|
|
513
685
|
controller: NativeController,
|
|
514
686
|
params: SingleComputerParams,
|
|
@@ -572,6 +744,64 @@ interface BatchDispatchResult {
|
|
|
572
744
|
screenshot?: ComputerScreenshotDetails;
|
|
573
745
|
screenshotSource?: unknown;
|
|
574
746
|
failedStep?: { code: string; message: string };
|
|
747
|
+
primaryError?: { code: string; message: string };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
interface BatchWireStep {
|
|
751
|
+
action: NativeBatchAction;
|
|
752
|
+
userIndex?: number;
|
|
753
|
+
finalScreenshot?: boolean;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function nativeBatchAction(params: SingleComputerParams, timeoutMs: number | undefined): NativeBatchAction {
|
|
757
|
+
const actionTimeoutMs = stepTimeoutFromParams(params, timeoutMs);
|
|
758
|
+
switch (params.action) {
|
|
759
|
+
case "screenshot":
|
|
760
|
+
return { action: "screenshot", timeoutMs: actionTimeoutMs };
|
|
761
|
+
case "click":
|
|
762
|
+
case "double_click":
|
|
763
|
+
case "move":
|
|
764
|
+
return {
|
|
765
|
+
action: params.action,
|
|
766
|
+
x: params.x,
|
|
767
|
+
y: params.y,
|
|
768
|
+
button: params.button,
|
|
769
|
+
timeoutMs: actionTimeoutMs,
|
|
770
|
+
};
|
|
771
|
+
case "drag":
|
|
772
|
+
return {
|
|
773
|
+
action: "drag",
|
|
774
|
+
x: params.x,
|
|
775
|
+
y: params.y,
|
|
776
|
+
toX: params.to_x,
|
|
777
|
+
toY: params.to_y,
|
|
778
|
+
button: params.button,
|
|
779
|
+
timeoutMs: actionTimeoutMs,
|
|
780
|
+
};
|
|
781
|
+
case "scroll":
|
|
782
|
+
return {
|
|
783
|
+
action: "scroll",
|
|
784
|
+
x: params.x,
|
|
785
|
+
y: params.y,
|
|
786
|
+
scrollX: params.scroll_x,
|
|
787
|
+
scrollY: params.scroll_y,
|
|
788
|
+
timeoutMs: actionTimeoutMs,
|
|
789
|
+
};
|
|
790
|
+
case "type":
|
|
791
|
+
return { action: "type", text: params.text, timeoutMs: actionTimeoutMs };
|
|
792
|
+
case "keypress":
|
|
793
|
+
return { action: "keypress", keys: params.keys, timeoutMs: actionTimeoutMs };
|
|
794
|
+
case "wait":
|
|
795
|
+
return { action: "wait", ms: capWaitMs(params.ms, actionTimeoutMs), timeoutMs: actionTimeoutMs };
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function mapNativeBatchFailure(
|
|
800
|
+
code: string,
|
|
801
|
+
message: string | undefined,
|
|
802
|
+
hotkey?: string,
|
|
803
|
+
): { code: string; message: string } {
|
|
804
|
+
return mapComputerError({ code, message: message ?? `${code}: Computer batch failed.` }, hotkey);
|
|
575
805
|
}
|
|
576
806
|
|
|
577
807
|
async function dispatchBatchComputerActions(
|
|
@@ -585,58 +815,100 @@ async function dispatchBatchComputerActions(
|
|
|
585
815
|
signal?: AbortSignal,
|
|
586
816
|
deadline?: ComputerDeadline,
|
|
587
817
|
): Promise<BatchDispatchResult> {
|
|
588
|
-
|
|
818
|
+
if (!controller.executeBatch) missingNativeMethod("batch", "executeBatch");
|
|
819
|
+
|
|
820
|
+
const steps = actions.map(detailsFromParams);
|
|
821
|
+
const wireSteps: BatchWireStep[] = [];
|
|
822
|
+
for (const [userIndex, action] of actions.entries()) {
|
|
823
|
+
const nativeAction = { ...nativeBatchAction(action, timeoutMs), timeoutGroup: userIndex };
|
|
824
|
+
wireSteps.push({ action: nativeAction, userIndex });
|
|
825
|
+
if (action.action !== "screenshot" && (action.include_screenshot === true || autoScreenshot)) {
|
|
826
|
+
wireSteps.push({
|
|
827
|
+
action: {
|
|
828
|
+
action: "screenshot",
|
|
829
|
+
timeoutMs: nativeAction.timeoutMs,
|
|
830
|
+
timeoutGroup: nativeAction.timeoutGroup,
|
|
831
|
+
},
|
|
832
|
+
userIndex,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (includeBatchScreenshot) {
|
|
837
|
+
wireSteps.push({ action: { action: "screenshot", timeoutMs }, finalScreenshot: true });
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
throwIfAborted(signal);
|
|
841
|
+
assertComputerDeadline(deadline);
|
|
842
|
+
const nativeTimeoutMs = remainingComputerTimeoutMs(deadline);
|
|
843
|
+
const nativeResult = await controller.executeBatch(
|
|
844
|
+
expectedEpochFromContext(initialContext),
|
|
845
|
+
wireSteps.map(step => step.action),
|
|
846
|
+
nativeTimeoutMs,
|
|
847
|
+
signal,
|
|
848
|
+
);
|
|
589
849
|
let lastScreenshot: ComputerScreenshotDetails | undefined;
|
|
590
850
|
let lastScreenshotSource: unknown;
|
|
591
|
-
|
|
592
|
-
for (const
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (
|
|
601
|
-
result = await captureScreenshot(controller, stepDeadline, signal);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const screenshot = normalizeScreenshot(result);
|
|
605
|
-
if (screenshot) {
|
|
606
|
-
stepDetails.screenshot = screenshot;
|
|
607
|
-
lastScreenshot = screenshot;
|
|
608
|
-
lastScreenshotSource = result;
|
|
609
|
-
context = screenshot;
|
|
610
|
-
}
|
|
611
|
-
stepDetails.status = "success";
|
|
612
|
-
stepDetails.message = describeComputerSuccess(stepDetails);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
if (error instanceof ToolAbortError) throw error;
|
|
615
|
-
const mapped = mapComputerError(error, hotkey);
|
|
616
|
-
stepDetails.status = mapped.code === COMPUTER_DISABLED_CODE ? "disabled" : "error";
|
|
617
|
-
stepDetails.code = mapped.code;
|
|
618
|
-
stepDetails.message = mapped.message;
|
|
619
|
-
steps.push(stepDetails);
|
|
620
|
-
return {
|
|
621
|
-
steps,
|
|
622
|
-
screenshot: lastScreenshot,
|
|
623
|
-
screenshotSource: lastScreenshotSource,
|
|
624
|
-
failedStep: { code: mapped.code, message: mapped.message },
|
|
625
|
-
};
|
|
851
|
+
const completedUserIndices = new Set<number>();
|
|
852
|
+
for (const result of nativeResult.results) {
|
|
853
|
+
const wireStep = wireSteps[result.index];
|
|
854
|
+
if (!wireStep) continue;
|
|
855
|
+
if (wireStep.userIndex !== undefined) completedUserIndices.add(wireStep.userIndex);
|
|
856
|
+
const screenshot = normalizeScreenshot(result);
|
|
857
|
+
if (screenshot) {
|
|
858
|
+
lastScreenshot = screenshot;
|
|
859
|
+
lastScreenshotSource = result.screenshot;
|
|
860
|
+
if (wireStep.userIndex !== undefined) steps[wireStep.userIndex]!.screenshot = screenshot;
|
|
626
861
|
}
|
|
627
|
-
steps.push(stepDetails);
|
|
628
862
|
}
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
|
|
863
|
+
if (!nativeResult.failureCode) {
|
|
864
|
+
throwIfAborted(signal);
|
|
865
|
+
assertComputerDeadline(deadline);
|
|
866
|
+
for (const step of steps) step.message = describeComputerSuccess(step);
|
|
867
|
+
return { steps, screenshot: lastScreenshot, screenshotSource: lastScreenshotSource };
|
|
632
868
|
}
|
|
633
|
-
|
|
869
|
+
|
|
870
|
+
const mapped = mapNativeBatchFailure(nativeResult.failureCode, nativeResult.failureMessage, hotkey);
|
|
871
|
+
const failedWireStep = wireSteps[nativeResult.failureIndex ?? nativeResult.results.length];
|
|
872
|
+
const primaryError =
|
|
873
|
+
nativeResult.primaryFailureCode === undefined
|
|
874
|
+
? undefined
|
|
875
|
+
: {
|
|
876
|
+
code: nativeResult.primaryFailureCode,
|
|
877
|
+
message: nativeResult.primaryFailureMessage ?? "Computer batch failed.",
|
|
878
|
+
};
|
|
879
|
+
if (failedWireStep?.userIndex !== undefined) {
|
|
880
|
+
const failed = steps[failedWireStep.userIndex]!;
|
|
881
|
+
failed.status = mapped.code === COMPUTER_DISABLED_CODE ? "disabled" : "error";
|
|
882
|
+
failed.code = mapped.code;
|
|
883
|
+
failed.message = mapped.message;
|
|
884
|
+
}
|
|
885
|
+
for (const index of completedUserIndices) {
|
|
886
|
+
const step = steps[index];
|
|
887
|
+
if (step && !step.message && step.status === "success") {
|
|
888
|
+
step.message = describeComputerSuccess(step);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const reportedIndices = new Set(completedUserIndices);
|
|
892
|
+
if (failedWireStep?.userIndex !== undefined) reportedIndices.add(failedWireStep.userIndex);
|
|
893
|
+
const reportedSteps = [...reportedIndices]
|
|
894
|
+
.sort((left, right) => left - right)
|
|
895
|
+
.map(index => steps[index]!)
|
|
896
|
+
.filter(Boolean);
|
|
897
|
+
return {
|
|
898
|
+
steps: reportedSteps,
|
|
899
|
+
screenshot: lastScreenshot,
|
|
900
|
+
screenshotSource: lastScreenshotSource,
|
|
901
|
+
failedStep: mapped,
|
|
902
|
+
primaryError,
|
|
903
|
+
};
|
|
634
904
|
}
|
|
635
905
|
|
|
636
906
|
function stepTimeoutFromParams(params: SingleComputerParams, batchTimeoutMs: number | undefined): number | undefined {
|
|
637
907
|
if (params.timeout === undefined) return batchTimeoutMs;
|
|
638
908
|
const timeoutSeconds = clampTimeout("computer", params.timeout);
|
|
639
|
-
|
|
909
|
+
const stepTimeoutMs = timeoutSeconds > 0 ? timeoutSeconds * 1000 : undefined;
|
|
910
|
+
if (stepTimeoutMs === undefined || batchTimeoutMs === undefined) return stepTimeoutMs ?? batchTimeoutMs;
|
|
911
|
+
return Math.min(stepTimeoutMs, batchTimeoutMs);
|
|
640
912
|
}
|
|
641
913
|
|
|
642
914
|
function detailsFromParams(params: ComputerParams): ComputerToolDetails {
|
|
@@ -821,6 +1093,9 @@ function mapComputerError(error: unknown, hotkey?: string): { code: string; mess
|
|
|
821
1093
|
"The host needs screen-recording or accessibility permission. Ask the user to grant it.",
|
|
822
1094
|
COMPUTER_DISABLED:
|
|
823
1095
|
"The computer tool is disabled or unsupported. Do not retry without enabling it on Apple Silicon macOS.",
|
|
1096
|
+
COMPUTER_SCREENSHOT_FAILED: "Capture failed. Check screen-recording permission and retry the screenshot.",
|
|
1097
|
+
COMPUTER_CURSOR_RESTORE_FAILED:
|
|
1098
|
+
"Input may have completed, but the cursor could not be restored. Stop and ask the user to inspect the desktop before retrying.",
|
|
824
1099
|
};
|
|
825
1100
|
const hint = recoveryHints[code];
|
|
826
1101
|
const message = hint ? `${code}: ${reason} ${hint}` : `${code}: ${reason}`;
|
|
@@ -832,6 +1107,7 @@ interface ComputerAuditRecord {
|
|
|
832
1107
|
action: ComputerActionName;
|
|
833
1108
|
status: "success" | "error" | "disabled";
|
|
834
1109
|
code?: string;
|
|
1110
|
+
primaryError?: { code: string; message: string };
|
|
835
1111
|
x?: number;
|
|
836
1112
|
y?: number;
|
|
837
1113
|
toX?: number;
|
|
@@ -853,6 +1129,7 @@ function auditRecordFromDetails(details: ComputerToolDetails): ComputerAuditReco
|
|
|
853
1129
|
status: details.status,
|
|
854
1130
|
};
|
|
855
1131
|
if (details.code) record.code = details.code;
|
|
1132
|
+
if (details.primaryError) record.primaryError = details.primaryError;
|
|
856
1133
|
if (details.x !== undefined) record.x = details.x;
|
|
857
1134
|
if (details.y !== undefined) record.y = details.y;
|
|
858
1135
|
if (details.toX !== undefined) record.toX = details.toX;
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface TodoPhase {
|
|
|
39
39
|
export interface TodoWriteToolDetails {
|
|
40
40
|
phases: TodoPhase[];
|
|
41
41
|
storage: "session" | "memory";
|
|
42
|
+
failureKind?: "payload_rejected" | "persistence";
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// =============================================================================
|
|
@@ -520,6 +521,9 @@ function formatSummary(phases: TodoPhase[], errors: string[]): string {
|
|
|
520
521
|
}
|
|
521
522
|
return lines.join("\n");
|
|
522
523
|
}
|
|
524
|
+
function formatPayloadRejectedSummary(phases: TodoPhase[], errors: string[]): string {
|
|
525
|
+
return `${formatSummary(phases, errors)}\nTodo update was not applied.`;
|
|
526
|
+
}
|
|
523
527
|
|
|
524
528
|
// =============================================================================
|
|
525
529
|
// Tool Class
|
|
@@ -547,26 +551,32 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
547
551
|
_context?: AgentToolContext,
|
|
548
552
|
): Promise<AgentToolResult<TodoWriteToolDetails>> {
|
|
549
553
|
const previousPhases = clonePhases(this.session.getTodoPhases?.() ?? []);
|
|
554
|
+
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
550
555
|
const missingTarget = params.ops.find(
|
|
551
556
|
entry => (entry.op === "done" || entry.op === "drop") && !entry.task && !entry.phase,
|
|
552
557
|
);
|
|
553
558
|
if (missingTarget) {
|
|
554
|
-
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
555
559
|
const errors = [`Missing task or phase for ${missingTarget.op} operation`];
|
|
556
560
|
return {
|
|
557
|
-
content: [{ type: "text", text:
|
|
558
|
-
details: { phases: previousPhases, storage },
|
|
561
|
+
content: [{ type: "text", text: formatPayloadRejectedSummary(previousPhases, errors) }],
|
|
562
|
+
details: { phases: previousPhases, storage, failureKind: "payload_rejected" },
|
|
559
563
|
isError: true,
|
|
560
564
|
};
|
|
561
565
|
}
|
|
562
|
-
const { phases: updated, errors } = applyParams(previousPhases, params);
|
|
563
|
-
|
|
564
|
-
|
|
566
|
+
const { phases: updated, errors } = applyParams(clonePhases(previousPhases), params);
|
|
567
|
+
const rejected = errors.length > 0;
|
|
568
|
+
if (!rejected) this.session.setTodoPhases?.(updated);
|
|
569
|
+
const phases = rejected ? previousPhases : updated;
|
|
565
570
|
|
|
566
571
|
return {
|
|
567
|
-
content: [
|
|
568
|
-
|
|
569
|
-
|
|
572
|
+
content: [
|
|
573
|
+
{
|
|
574
|
+
type: "text",
|
|
575
|
+
text: rejected ? formatPayloadRejectedSummary(phases, errors) : formatSummary(phases, errors),
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
details: rejected ? { phases, storage, failureKind: "payload_rejected" } : { phases, storage },
|
|
579
|
+
isError: rejected || undefined,
|
|
570
580
|
};
|
|
571
581
|
}
|
|
572
582
|
}
|
|
@@ -698,19 +708,27 @@ export const todoWriteToolRenderer = {
|
|
|
698
708
|
},
|
|
699
709
|
|
|
700
710
|
renderResult(
|
|
701
|
-
result: {
|
|
711
|
+
result: {
|
|
712
|
+
content: Array<{ type: string; text?: string }>;
|
|
713
|
+
details?: TodoWriteToolDetails;
|
|
714
|
+
isError?: boolean;
|
|
715
|
+
},
|
|
702
716
|
options: RenderResultOptions,
|
|
703
717
|
uiTheme: Theme,
|
|
704
718
|
_args?: TodoWriteRenderArgs,
|
|
705
719
|
): Component {
|
|
706
720
|
const phases = (result.details?.phases ?? []).filter(phase => phase.tasks.length > 0);
|
|
707
721
|
const allTasks = phases.flatMap(phase => phase.tasks);
|
|
722
|
+
const fallback = result.content?.find(content => content.type === "text")?.text ?? "No todos";
|
|
723
|
+
if (result.isError) {
|
|
724
|
+
const header = renderStatusLine({ icon: "error", title: "Todo Write" }, uiTheme);
|
|
725
|
+
return new Text(`${header}\n${uiTheme.fg("error", fallback)}`, 0, 0);
|
|
726
|
+
}
|
|
708
727
|
const header = renderStatusLine(
|
|
709
728
|
{ icon: "success", title: "Todo Write", meta: [`${allTasks.length} tasks`] },
|
|
710
729
|
uiTheme,
|
|
711
730
|
);
|
|
712
731
|
if (allTasks.length === 0) {
|
|
713
|
-
const fallback = result.content?.find(content => content.type === "text")?.text ?? "No todos";
|
|
714
732
|
return new Text(`${header}\n${uiTheme.fg("dim", fallback)}`, 0, 0);
|
|
715
733
|
}
|
|
716
734
|
|