tinker-agent 1.6.0 → 1.7.0

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.
@@ -0,0 +1,296 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import { TERMINAL_SCREEN_COLUMNS, TERMINAL_SCREEN_ROWS } from "./terminal-screen";
3
+
4
+ export type ShellProcessMode = "pipe" | "pty";
5
+
6
+ export type ProcessExitResult = {
7
+ code: number | null;
8
+ signal: NodeJS.Signals | null;
9
+ error?: string;
10
+ };
11
+
12
+ export type ShellProcessHandle = {
13
+ readonly pid: number;
14
+ readonly mode: ShellProcessMode;
15
+ readonly exitCode: number | null;
16
+ readonly signalCode: NodeJS.Signals | null;
17
+ wait(): Promise<ProcessExitResult>;
18
+ waitForOutputClose(): Promise<void>;
19
+ write?(chars: string): Promise<number>;
20
+ close(): void;
21
+ };
22
+
23
+ export class ShellProcessWriteError extends Error {
24
+ constructor(
25
+ message: string,
26
+ readonly writtenBytes: number,
27
+ ) {
28
+ super(message);
29
+ this.name = "ShellProcessWriteError";
30
+ }
31
+ }
32
+
33
+ export async function spawnShellProcess(input: {
34
+ mode: ShellProcessMode;
35
+ command: string;
36
+ cwd: string;
37
+ cwdFilePath: string;
38
+ onOutput(bytes: Uint8Array): void;
39
+ }): Promise<ShellProcessHandle> {
40
+ const env = {
41
+ ...process.env,
42
+ NO_COLOR: "1",
43
+ TINKER_BASH_COMMAND: input.command,
44
+ TINKER_BASH_CWD_FILE: input.cwdFilePath,
45
+ };
46
+
47
+ if (input.mode === "pty") {
48
+ return spawnPtyShellProcess({ ...input, env });
49
+ }
50
+
51
+ return spawnPipeShellProcess({ ...input, env });
52
+ }
53
+
54
+ async function spawnPipeShellProcess(input: {
55
+ cwd: string;
56
+ env: NodeJS.ProcessEnv;
57
+ onOutput(bytes: Uint8Array): void;
58
+ }): Promise<ShellProcessHandle> {
59
+ const child = spawn("bash", ["-lc", bashWrapperScript], {
60
+ cwd: input.cwd,
61
+ detached: true,
62
+ env: input.env,
63
+ });
64
+ pipeOutput(child.stdout, (bytes) => input.onOutput(bytes));
65
+ pipeOutput(child.stderr, (bytes) => input.onOutput(bytes));
66
+
67
+ const exit = waitForNodeProcessExit(child);
68
+ const close = waitForNodeProcessClose(child);
69
+ if (child.pid === undefined) {
70
+ const result = await exit;
71
+ throw new Error(result.error ?? "Bash process failed to start.");
72
+ }
73
+
74
+ return {
75
+ pid: child.pid,
76
+ mode: "pipe",
77
+ get exitCode() {
78
+ return child.exitCode;
79
+ },
80
+ get signalCode() {
81
+ return child.signalCode;
82
+ },
83
+ wait: () => exit,
84
+ waitForOutputClose: () => close,
85
+ close() {},
86
+ };
87
+ }
88
+
89
+ function spawnPtyShellProcess(input: {
90
+ cwd: string;
91
+ env: NodeJS.ProcessEnv;
92
+ onOutput(bytes: Uint8Array): void;
93
+ }): ShellProcessHandle {
94
+ let terminalEnded = false;
95
+ let resolveTerminalExit: (() => void) | undefined;
96
+ let drainGeneration = 0;
97
+ const drainWaiters = new Set<() => void>();
98
+ const terminalExit = new Promise<void>((resolve) => {
99
+ resolveTerminalExit = resolve;
100
+ });
101
+ const notifyDrain = () => {
102
+ drainGeneration += 1;
103
+ for (const resolve of drainWaiters) {
104
+ resolve();
105
+ }
106
+ drainWaiters.clear();
107
+ };
108
+ const settleTerminalExit = () => {
109
+ if (terminalEnded) {
110
+ return;
111
+ }
112
+ terminalEnded = true;
113
+ notifyDrain();
114
+ resolveTerminalExit?.();
115
+ };
116
+
117
+ const subprocess = Bun.spawn(["bash", "-lc", bashWrapperScript], {
118
+ cwd: input.cwd,
119
+ detached: true,
120
+ env: {
121
+ ...input.env,
122
+ TERM: "xterm-256color",
123
+ NO_COLOR: "1",
124
+ PAGER: "cat",
125
+ GIT_PAGER: "cat",
126
+ },
127
+ terminal: {
128
+ cols: TERMINAL_SCREEN_COLUMNS,
129
+ rows: TERMINAL_SCREEN_ROWS,
130
+ name: "xterm-256color",
131
+ data(_terminal, bytes) {
132
+ input.onOutput(new Uint8Array(bytes));
133
+ },
134
+ exit() {
135
+ // Linux reports slave closure as EIO; subprocess.exited owns task status.
136
+ settleTerminalExit();
137
+ },
138
+ drain() {
139
+ notifyDrain();
140
+ },
141
+ },
142
+ });
143
+ const terminal = subprocess.terminal!;
144
+
145
+ let writeQueue = Promise.resolve();
146
+ const write = (chars: string): Promise<number> => {
147
+ const bytes = new TextEncoder().encode(chars);
148
+ const operation = writeQueue.then(async () => {
149
+ let writtenBytes = 0;
150
+ try {
151
+ while (writtenBytes < bytes.byteLength) {
152
+ if (terminalEnded || terminal.closed) {
153
+ throw new Error("PTY is closed.");
154
+ }
155
+
156
+ const generationBeforeWrite = drainGeneration;
157
+ const accepted = terminal.write(bytes.subarray(writtenBytes));
158
+ if (accepted < 0 || accepted > bytes.byteLength - writtenBytes) {
159
+ throw new Error(`PTY accepted an invalid byte count: ${accepted}.`);
160
+ }
161
+ writtenBytes += accepted;
162
+
163
+ if (writtenBytes < bytes.byteLength) {
164
+ await waitForDrainOrExit({
165
+ generationBeforeWrite,
166
+ currentGeneration: () => drainGeneration,
167
+ terminalEnded: () => terminalEnded || terminal.closed,
168
+ drainWaiters,
169
+ });
170
+ }
171
+ }
172
+ return writtenBytes;
173
+ } catch (error) {
174
+ throw new ShellProcessWriteError(
175
+ error instanceof Error ? error.message : String(error),
176
+ writtenBytes,
177
+ );
178
+ }
179
+ });
180
+ writeQueue = operation.then(
181
+ () => undefined,
182
+ () => undefined,
183
+ );
184
+ return operation;
185
+ };
186
+
187
+ const exit = subprocess.exited.then(
188
+ (code): ProcessExitResult => ({
189
+ code: subprocess.signalCode === null ? code : null,
190
+ signal: subprocess.signalCode,
191
+ }),
192
+ (error): ProcessExitResult => ({
193
+ code: null,
194
+ signal: null,
195
+ error: error instanceof Error ? error.message : String(error),
196
+ }),
197
+ );
198
+
199
+ return {
200
+ pid: subprocess.pid,
201
+ mode: "pty",
202
+ get exitCode() {
203
+ return subprocess.exitCode;
204
+ },
205
+ get signalCode() {
206
+ return subprocess.signalCode;
207
+ },
208
+ wait: () => exit,
209
+ waitForOutputClose: () => terminalExit,
210
+ write,
211
+ close() {
212
+ if (!terminal.closed) {
213
+ terminal.close();
214
+ }
215
+ },
216
+ };
217
+ }
218
+
219
+ async function waitForDrainOrExit(input: {
220
+ generationBeforeWrite: number;
221
+ currentGeneration(): number;
222
+ terminalEnded(): boolean;
223
+ drainWaiters: Set<() => void>;
224
+ }): Promise<void> {
225
+ if (
226
+ input.currentGeneration() !== input.generationBeforeWrite ||
227
+ input.terminalEnded()
228
+ ) {
229
+ return;
230
+ }
231
+
232
+ await new Promise<void>((resolve) => {
233
+ input.drainWaiters.add(resolve);
234
+ if (
235
+ input.currentGeneration() !== input.generationBeforeWrite ||
236
+ input.terminalEnded()
237
+ ) {
238
+ input.drainWaiters.delete(resolve);
239
+ resolve();
240
+ }
241
+ });
242
+ }
243
+
244
+ function waitForNodeProcessExit(
245
+ process: ChildProcessWithoutNullStreams,
246
+ ): Promise<ProcessExitResult> {
247
+ return new Promise((resolve) => {
248
+ let settled = false;
249
+ const finish = (result: ProcessExitResult) => {
250
+ if (!settled) {
251
+ settled = true;
252
+ resolve(result);
253
+ }
254
+ };
255
+
256
+ process.once("error", (error) => {
257
+ finish({ code: null, signal: null, error: error.message });
258
+ });
259
+ process.once("exit", (code, signal) => {
260
+ finish({ code, signal });
261
+ });
262
+ });
263
+ }
264
+
265
+ function waitForNodeProcessClose(
266
+ process: ChildProcessWithoutNullStreams,
267
+ ): Promise<void> {
268
+ return new Promise((resolve) => {
269
+ let settled = false;
270
+ const finish = () => {
271
+ if (!settled) {
272
+ settled = true;
273
+ resolve();
274
+ }
275
+ };
276
+
277
+ process.once("error", finish);
278
+ process.once("close", finish);
279
+ });
280
+ }
281
+
282
+ function pipeOutput(
283
+ stream: NodeJS.ReadableStream,
284
+ onOutput: (bytes: Uint8Array) => void,
285
+ ): void {
286
+ stream.on("data", (chunk: Buffer | string) => {
287
+ onOutput(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
288
+ });
289
+ }
290
+
291
+ const bashWrapperScript = `
292
+ eval "$TINKER_BASH_COMMAND"
293
+ exit_code=$?
294
+ pwd -P > "$TINKER_BASH_CWD_FILE"
295
+ exit "$exit_code"
296
+ `;
@@ -0,0 +1,229 @@
1
+ import { throwIfTurnCancelled } from "../agent/turn-cancellation";
2
+ import type { ShellTaskManager, ShellTaskSnapshot } from "./bash-task";
3
+ import { ShellProcessWriteError } from "./shell-process";
4
+ import { defineToolExecutor } from "./types";
5
+ import type { TaskInputRawResult, ToolExecutionContext, ToolExecutor } from "./types";
6
+
7
+ type TaskInputArgs = {
8
+ taskId: string;
9
+ chars: string;
10
+ waitMs: number;
11
+ };
12
+
13
+ const defaultWaitMs = 250;
14
+ const maxWaitMs = 30_000;
15
+
16
+ export function createTaskInputToolExecutor(options: {
17
+ taskManager: ShellTaskManager;
18
+ }): ToolExecutor {
19
+ return defineToolExecutor("task_input", {
20
+ definition: {
21
+ name: "TaskInput",
22
+ description:
23
+ "Write characters to a PTY shell task and return its current terminal screen.",
24
+ parameters: {
25
+ type: "object",
26
+ additionalProperties: false,
27
+ properties: {
28
+ task_id: {
29
+ type: "string",
30
+ description: "The PTY task ID returned by Bash or TaskList.",
31
+ },
32
+ chars: {
33
+ type: "string",
34
+ description:
35
+ "Characters to write exactly as provided. Use an empty string to poll without writing.",
36
+ },
37
+ wait_ms: {
38
+ type: "integer",
39
+ minimum: 0,
40
+ maximum: maxWaitMs,
41
+ description:
42
+ "Milliseconds to wait before returning the current screen. Defaults to 250.",
43
+ },
44
+ },
45
+ required: ["task_id", "chars"],
46
+ },
47
+ },
48
+ async execute(
49
+ args,
50
+ _call,
51
+ context: ToolExecutionContext,
52
+ ): Promise<TaskInputRawResult> {
53
+ throwIfTurnCancelled(context.signal);
54
+ const parsed = parseTaskInputArgs(args);
55
+ if (!parsed.ok) {
56
+ return { ok: false, taskId: "", error: parsed.error };
57
+ }
58
+
59
+ const { taskId, chars, waitMs } = parsed.value;
60
+ const initial = options.taskManager.inspectTask(taskId);
61
+ if (initial === undefined) {
62
+ return { ok: false, taskId, error: `Unknown task ID: ${taskId}` };
63
+ }
64
+ if (!initial.task.tty) {
65
+ return taskInputFailure(
66
+ initial.task,
67
+ `Task ${taskId} does not accept terminal input; start it with Bash tty=true.`,
68
+ );
69
+ }
70
+ if (chars !== "" && initial.task.status !== "running") {
71
+ return taskInputFailure(
72
+ initial.task,
73
+ `Task ${taskId} is not running (status=${initial.task.status}).`,
74
+ );
75
+ }
76
+
77
+ let writtenBytes = 0;
78
+ if (chars !== "") {
79
+ try {
80
+ writtenBytes = await options.taskManager.writeTaskInput(taskId, chars);
81
+ } catch (error) {
82
+ const current = options.taskManager.inspectTask(taskId)?.task ?? initial.task;
83
+ return {
84
+ ...taskInputFailure(
85
+ current,
86
+ error instanceof Error ? error.message : String(error),
87
+ ),
88
+ writtenBytes:
89
+ error instanceof ShellProcessWriteError
90
+ ? error.writtenBytes
91
+ : writtenBytes,
92
+ };
93
+ }
94
+ }
95
+
96
+ throwIfTurnCancelled(context.signal);
97
+ const waitStartedAt = Date.now();
98
+ await waitForCollectionWindow({
99
+ waitMs,
100
+ completion: options.taskManager.taskCompletion(taskId),
101
+ signal: context.signal,
102
+ });
103
+ throwIfTurnCancelled(context.signal);
104
+
105
+ const inspection = await options.taskManager.inspectTaskOutput(taskId);
106
+ if (inspection === undefined) {
107
+ return {
108
+ ok: false,
109
+ taskId,
110
+ writtenBytes,
111
+ error: `Task disappeared while waiting for terminal output: ${taskId}`,
112
+ };
113
+ }
114
+ if (
115
+ inspection.screen === undefined ||
116
+ inspection.screenRows === undefined ||
117
+ inspection.screenColumns === undefined
118
+ ) {
119
+ throw new Error(`PTY task ${taskId} has no terminal screen.`);
120
+ }
121
+
122
+ return {
123
+ ok: true,
124
+ taskId,
125
+ task: inspection.task,
126
+ status: inspection.task.status,
127
+ writtenBytes,
128
+ waitedMs: Math.max(0, Date.now() - waitStartedAt),
129
+ screenRows: inspection.screenRows,
130
+ screenColumns: inspection.screenColumns,
131
+ screen: inspection.screen,
132
+ outputBytes: inspection.output.outputBytes,
133
+ outputLines: inspection.output.outputLines,
134
+ outputFilePath: inspection.task.outputFilePath,
135
+ };
136
+ },
137
+ });
138
+ }
139
+
140
+ export function parseTaskInputArgs(
141
+ args: unknown,
142
+ ): { ok: true; value: TaskInputArgs } | { ok: false; error: string } {
143
+ if (!isRecord(args)) {
144
+ return { ok: false, error: "TaskInput arguments must be an object." };
145
+ }
146
+
147
+ const allowed = new Set(["task_id", "chars", "wait_ms"]);
148
+ const unexpected = Object.keys(args).find((key) => !allowed.has(key));
149
+ if (unexpected !== undefined) {
150
+ return {
151
+ ok: false,
152
+ error: `TaskInput received unexpected argument: ${unexpected}.`,
153
+ };
154
+ }
155
+ if (typeof args.task_id !== "string" || args.task_id.trim() === "") {
156
+ return { ok: false, error: "TaskInput.task_id must be a non-empty string." };
157
+ }
158
+ if (typeof args.chars !== "string") {
159
+ return { ok: false, error: "TaskInput.chars must be a string." };
160
+ }
161
+ if (
162
+ args.wait_ms !== undefined &&
163
+ (!Number.isInteger(args.wait_ms) ||
164
+ typeof args.wait_ms !== "number" ||
165
+ args.wait_ms < 0 ||
166
+ args.wait_ms > maxWaitMs)
167
+ ) {
168
+ return {
169
+ ok: false,
170
+ error: `TaskInput.wait_ms must be an integer between 0 and ${maxWaitMs}.`,
171
+ };
172
+ }
173
+
174
+ return {
175
+ ok: true,
176
+ value: {
177
+ taskId: args.task_id,
178
+ chars: args.chars,
179
+ waitMs: args.wait_ms ?? defaultWaitMs,
180
+ },
181
+ };
182
+ }
183
+
184
+ function taskInputFailure(task: ShellTaskSnapshot, error: string): TaskInputRawResult {
185
+ return {
186
+ ok: false,
187
+ taskId: task.taskId,
188
+ task,
189
+ status: task.status,
190
+ error,
191
+ };
192
+ }
193
+
194
+ async function waitForCollectionWindow(input: {
195
+ waitMs: number;
196
+ completion: Promise<ShellTaskSnapshot>;
197
+ signal: AbortSignal;
198
+ }): Promise<void> {
199
+ let timeout: ReturnType<typeof setTimeout> | undefined;
200
+ let onAbort: (() => void) | undefined;
201
+
202
+ try {
203
+ await Promise.race([
204
+ input.completion.then(() => undefined),
205
+ new Promise<void>((resolve) => {
206
+ timeout = setTimeout(resolve, input.waitMs);
207
+ }),
208
+ new Promise<void>((resolve) => {
209
+ onAbort = () => resolve();
210
+ if (input.signal.aborted) {
211
+ onAbort();
212
+ return;
213
+ }
214
+ input.signal.addEventListener("abort", onAbort, { once: true });
215
+ }),
216
+ ]);
217
+ } finally {
218
+ if (timeout !== undefined) {
219
+ clearTimeout(timeout);
220
+ }
221
+ if (onAbort !== undefined) {
222
+ input.signal.removeEventListener("abort", onAbort);
223
+ }
224
+ }
225
+ }
226
+
227
+ function isRecord(value: unknown): value is Record<string, unknown> {
228
+ return typeof value === "object" && value !== null && !Array.isArray(value);
229
+ }
@@ -34,7 +34,7 @@ export function createTaskOutputToolExecutor(options: {
34
34
  return { ok: false, taskId: "", error: parsed.error };
35
35
  }
36
36
 
37
- const inspection = options.taskManager.inspectTask(parsed.taskId);
37
+ const inspection = await options.taskManager.inspectTaskOutput(parsed.taskId);
38
38
  throwIfTurnCancelled(context.signal);
39
39
  if (inspection === undefined) {
40
40
  return {
@@ -56,6 +56,9 @@ export function createTaskOutputToolExecutor(options: {
56
56
  truncated: inspection.output.truncated,
57
57
  omittedLines: inspection.output.omittedLines,
58
58
  outputFilePath: inspection.task.outputFilePath,
59
+ screenRows: inspection.screenRows,
60
+ screenColumns: inspection.screenColumns,
61
+ screen: inspection.screen,
59
62
  };
60
63
  },
61
64
  });
@@ -0,0 +1,105 @@
1
+ import { Unicode11Addon } from "@xterm/addon-unicode11";
2
+ import { Terminal } from "@xterm/headless";
3
+
4
+ export const TERMINAL_SCREEN_ROWS = 24;
5
+ export const TERMINAL_SCREEN_COLUMNS = 80;
6
+
7
+ export type TerminalScreen = {
8
+ write(bytes: Uint8Array): Promise<void>;
9
+ flush(): Promise<void>;
10
+ text(): string;
11
+ dispose(): void;
12
+ };
13
+
14
+ export function createTerminalScreen(): TerminalScreen {
15
+ return new HeadlessTerminalScreen(TERMINAL_SCREEN_ROWS, TERMINAL_SCREEN_COLUMNS);
16
+ }
17
+
18
+ export class HeadlessTerminalScreen implements TerminalScreen {
19
+ private readonly terminal: Terminal;
20
+ private readonly unicodeAddon: Unicode11Addon;
21
+ private pendingWrite = Promise.resolve();
22
+ private disposed = false;
23
+ private currentRows: number;
24
+ private currentColumns: number;
25
+
26
+ constructor(rows: number, columns: number) {
27
+ this.currentRows = rows;
28
+ this.currentColumns = columns;
29
+ this.terminal = new Terminal({
30
+ allowProposedApi: true,
31
+ cols: columns,
32
+ rows,
33
+ scrollback: 0,
34
+ });
35
+ this.unicodeAddon = new Unicode11Addon();
36
+ this.terminal.loadAddon(this.unicodeAddon);
37
+ this.terminal.unicode.activeVersion = "11";
38
+ }
39
+
40
+ get rows(): number {
41
+ return this.currentRows;
42
+ }
43
+
44
+ get columns(): number {
45
+ return this.currentColumns;
46
+ }
47
+
48
+ get bracketedPasteMode(): boolean {
49
+ return this.terminal.modes.bracketedPasteMode;
50
+ }
51
+
52
+ write(bytes: Uint8Array): Promise<void> {
53
+ const copy = new Uint8Array(bytes);
54
+ const pending = this.pendingWrite.then(
55
+ () =>
56
+ new Promise<void>((resolve, reject) => {
57
+ if (this.disposed) {
58
+ reject(new Error("Cannot write to a disposed terminal screen."));
59
+ return;
60
+ }
61
+
62
+ try {
63
+ this.terminal.write(copy, resolve);
64
+ } catch (error) {
65
+ reject(error instanceof Error ? error : new Error(String(error)));
66
+ }
67
+ }),
68
+ );
69
+ this.pendingWrite = pending;
70
+ return pending;
71
+ }
72
+
73
+ async resize(rows: number, columns: number): Promise<void> {
74
+ await this.pendingWrite;
75
+ this.terminal.resize(columns, rows);
76
+ this.currentRows = rows;
77
+ this.currentColumns = columns;
78
+ }
79
+
80
+ async flush(): Promise<void> {
81
+ await this.pendingWrite;
82
+ }
83
+
84
+ text(): string {
85
+ const buffer = this.terminal.buffer.active;
86
+ const lines: string[] = [];
87
+ for (let row = 0; row < this.rows; row += 1) {
88
+ lines.push(buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? "");
89
+ }
90
+ while (lines.at(-1) === "") {
91
+ lines.pop();
92
+ }
93
+ return lines.join("\n");
94
+ }
95
+
96
+ dispose(): void {
97
+ if (this.disposed) {
98
+ return;
99
+ }
100
+
101
+ this.disposed = true;
102
+ this.unicodeAddon.dispose();
103
+ this.terminal.dispose();
104
+ }
105
+ }
@@ -130,6 +130,10 @@ export type BashRawResult = {
130
130
  backgrounded?: boolean;
131
131
  backgroundedDueToTimeout?: boolean;
132
132
  returnCodeInterpretation?: string;
133
+ tty: boolean;
134
+ screenRows?: number;
135
+ screenColumns?: number;
136
+ screen?: string;
133
137
  error?: string;
134
138
  };
135
139
 
@@ -152,9 +156,36 @@ export type TaskOutputRawResult = {
152
156
  truncated?: boolean;
153
157
  omittedLines?: number;
154
158
  outputFilePath?: string;
159
+ screenRows?: number;
160
+ screenColumns?: number;
161
+ screen?: string;
155
162
  error?: string;
156
163
  };
157
164
 
165
+ export type TaskInputRawResult =
166
+ | {
167
+ ok: true;
168
+ taskId: string;
169
+ task: ShellTaskSnapshot;
170
+ status: ShellTaskStatus;
171
+ writtenBytes: number;
172
+ waitedMs: number;
173
+ screenRows: number;
174
+ screenColumns: number;
175
+ screen: string;
176
+ outputBytes: number;
177
+ outputLines: number;
178
+ outputFilePath: string;
179
+ }
180
+ | {
181
+ ok: false;
182
+ taskId: string;
183
+ task?: ShellTaskSnapshot;
184
+ status?: ShellTaskStatus;
185
+ writtenBytes?: number;
186
+ error: string;
187
+ };
188
+
158
189
  export type TaskStopRawResult = {
159
190
  ok: boolean;
160
191
  taskId: string;
@@ -326,6 +357,7 @@ export type ToolRawResultByKind = {
326
357
  bash: BashRawResult;
327
358
  task_list: TaskListRawResult;
328
359
  task_output: TaskOutputRawResult;
360
+ task_input: TaskInputRawResult;
329
361
  task_stop: TaskStopRawResult;
330
362
  web_search: WebSearchRawResult;
331
363
  web_fetch: WebFetchRawResult;