@kenkaiiii/gg-agent 5.68.1 → 5.69.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.
- package/dist/index.cjs +138 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +138 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -253,6 +253,29 @@ ${rule.reminder.trim()}
|
|
|
253
253
|
// src/agent-loop.ts
|
|
254
254
|
var DEFAULT_MAX_TURNS = 300;
|
|
255
255
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
256
|
+
var PREEMPT_GRACE_MS = 500;
|
|
257
|
+
var STEER_INTERRUPTED_TEXT = "Interrupted: the user sent a new message.";
|
|
258
|
+
var ATOMIC_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "multi_edit", "apply_patch", "notebook_edit"]);
|
|
259
|
+
function isInterruptibleTool(tool, name) {
|
|
260
|
+
if (tool?.interruptible !== void 0) return tool.interruptible;
|
|
261
|
+
return !ATOMIC_TOOL_NAMES.has(name);
|
|
262
|
+
}
|
|
263
|
+
function markSteerInterrupted(record) {
|
|
264
|
+
const content = record.content;
|
|
265
|
+
let next;
|
|
266
|
+
if (typeof content === "string") {
|
|
267
|
+
next = content.trim() ? `${STEER_INTERRUPTED_TEXT}
|
|
268
|
+
Partial output:
|
|
269
|
+
${content}` : STEER_INTERRUPTED_TEXT;
|
|
270
|
+
} else {
|
|
271
|
+
next = [
|
|
272
|
+
{ type: "text", text: `${STEER_INTERRUPTED_TEXT}
|
|
273
|
+
Partial output:` },
|
|
274
|
+
...content
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
return { toolCallId: record.toolCallId, content: next, isError: true };
|
|
278
|
+
}
|
|
256
279
|
var _toolRedaction;
|
|
257
280
|
function toolRedactionOptions() {
|
|
258
281
|
_toolRedaction ??= { secrets: (0, import_gg_ai.environmentSecrets)(process.env) };
|
|
@@ -1403,10 +1426,14 @@ async function* agentLoop(messages, options) {
|
|
|
1403
1426
|
fatalToolArgumentRecoverable = recoverable;
|
|
1404
1427
|
fatalToolArgumentToolName = toolName;
|
|
1405
1428
|
};
|
|
1429
|
+
const preempt = new AbortController();
|
|
1430
|
+
const unsubscribeSteering = options.onSteeringAvailable?.(() => preempt.abort());
|
|
1406
1431
|
const executionOptions = {
|
|
1407
1432
|
signal: options.signal,
|
|
1433
|
+
preemptSignal: options.onSteeringAvailable ? preempt.signal : void 0,
|
|
1408
1434
|
maxToolResultChars: options.maxToolResultChars,
|
|
1409
1435
|
maxTurnToolResultChars: options.maxTurnToolResultChars,
|
|
1436
|
+
transformToolResult: options.transformToolResult,
|
|
1410
1437
|
toolMap,
|
|
1411
1438
|
invalidToolArgumentCounts,
|
|
1412
1439
|
markFatalToolArgumentError,
|
|
@@ -1415,7 +1442,15 @@ async function* agentLoop(messages, options) {
|
|
|
1415
1442
|
const hasSequentialToolCall = toolCalls.some(
|
|
1416
1443
|
(toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
|
|
1417
1444
|
);
|
|
1418
|
-
|
|
1445
|
+
let executionResult;
|
|
1446
|
+
try {
|
|
1447
|
+
executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1448
|
+
} finally {
|
|
1449
|
+
unsubscribeSteering?.();
|
|
1450
|
+
}
|
|
1451
|
+
if (executionResult.preempted) {
|
|
1452
|
+
diag("steer_preempted_tools", { turn, provider: options.provider, model: options.model });
|
|
1453
|
+
}
|
|
1419
1454
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1420
1455
|
yield { type: "checkpoint", turn };
|
|
1421
1456
|
const toolsAborted = executionResult.aborted;
|
|
@@ -1589,8 +1624,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1589
1624
|
}
|
|
1590
1625
|
const callerSignal = options.signal;
|
|
1591
1626
|
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1627
|
+
const preemptSignal = isInterruptibleTool(tool, toolCall.name) ? options.preemptSignal : void 0;
|
|
1628
|
+
const toolSignals = [callerSignal, toolTimeout, preemptSignal].filter(
|
|
1629
|
+
(s) => s !== void 0
|
|
1630
|
+
);
|
|
1592
1631
|
const ctx = {
|
|
1593
|
-
signal:
|
|
1632
|
+
signal: toolSignals.length === 1 ? toolTimeout : AbortSignal.any(toolSignals),
|
|
1594
1633
|
toolCallId: toolCall.id,
|
|
1595
1634
|
onUpdate: (update) => {
|
|
1596
1635
|
pushEvent({
|
|
@@ -1604,6 +1643,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1604
1643
|
const normalized = normalizeToolResult(raw);
|
|
1605
1644
|
resultContent = (0, import_gg_ai.redactValue)(normalized.content, toolRedactionOptions());
|
|
1606
1645
|
details = (0, import_gg_ai.redactValue)(normalized.details, toolRedactionOptions());
|
|
1646
|
+
if (options.transformToolResult) {
|
|
1647
|
+
try {
|
|
1648
|
+
resultContent = options.transformToolResult(
|
|
1649
|
+
{ name: toolCall.name, args: toolCall.args },
|
|
1650
|
+
resultContent
|
|
1651
|
+
);
|
|
1652
|
+
} catch {
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1607
1655
|
for (const key of options.invalidToolArgumentCounts.keys()) {
|
|
1608
1656
|
if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
|
|
1609
1657
|
}
|
|
@@ -1641,6 +1689,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1641
1689
|
}
|
|
1642
1690
|
resultContent = (0, import_gg_ai.redactValue)(resultContent, toolRedactionOptions());
|
|
1643
1691
|
details = (0, import_gg_ai.redactValue)(details, toolRedactionOptions());
|
|
1692
|
+
if (options.preemptSignal?.aborted && isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name)) {
|
|
1693
|
+
const marked = markSteerInterrupted({
|
|
1694
|
+
toolCallId: toolCall.id,
|
|
1695
|
+
content: resultContent,
|
|
1696
|
+
isError
|
|
1697
|
+
});
|
|
1698
|
+
resultContent = marked.content;
|
|
1699
|
+
isError = true;
|
|
1700
|
+
}
|
|
1644
1701
|
const durationMs = Date.now() - startTime;
|
|
1645
1702
|
pushEvent({
|
|
1646
1703
|
type: "tool_call_end",
|
|
@@ -1653,6 +1710,59 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1653
1710
|
});
|
|
1654
1711
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1655
1712
|
}
|
|
1713
|
+
function createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds) {
|
|
1714
|
+
const running = /* @__PURE__ */ new Map();
|
|
1715
|
+
let graceTimer;
|
|
1716
|
+
const preemptSignal = options.preemptSignal;
|
|
1717
|
+
const abandon = () => {
|
|
1718
|
+
graceTimer = void 0;
|
|
1719
|
+
for (const [id, entry] of running) {
|
|
1720
|
+
if (!entry.interruptible || resultsById.has(id)) continue;
|
|
1721
|
+
resultsById.set(id, { toolCallId: id, content: STEER_INTERRUPTED_TEXT, isError: true });
|
|
1722
|
+
pushToolEvent(eventStream, state, {
|
|
1723
|
+
type: "tool_call_end",
|
|
1724
|
+
toolCallId: id,
|
|
1725
|
+
result: STEER_INTERRUPTED_TEXT,
|
|
1726
|
+
isError: true,
|
|
1727
|
+
durationMs: Date.now() - entry.startedAt
|
|
1728
|
+
});
|
|
1729
|
+
running.delete(id);
|
|
1730
|
+
}
|
|
1731
|
+
if (running.size === 0) eventStream.close();
|
|
1732
|
+
};
|
|
1733
|
+
const onPreempt = () => {
|
|
1734
|
+
if (graceTimer === void 0) graceTimer = setTimeout(abandon, PREEMPT_GRACE_MS);
|
|
1735
|
+
};
|
|
1736
|
+
preemptSignal?.addEventListener("abort", onPreempt, { once: true });
|
|
1737
|
+
return {
|
|
1738
|
+
async run(toolCall) {
|
|
1739
|
+
if (options.preemptSignal?.aborted) return;
|
|
1740
|
+
dispatchedIds.add(toolCall.id);
|
|
1741
|
+
const interruptible = isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name);
|
|
1742
|
+
running.set(toolCall.id, { toolCall, interruptible, startedAt: Date.now() });
|
|
1743
|
+
const record = await executeSingleToolCall(
|
|
1744
|
+
toolCall,
|
|
1745
|
+
options,
|
|
1746
|
+
(event) => pushToolEvent(eventStream, state, event)
|
|
1747
|
+
);
|
|
1748
|
+
running.delete(toolCall.id);
|
|
1749
|
+
if (resultsById.has(toolCall.id)) return;
|
|
1750
|
+
const alreadyMarked = typeof record.content === "string" ? record.content.startsWith(STEER_INTERRUPTED_TEXT) : record.content[0]?.type === "text" && record.content[0].text.startsWith(STEER_INTERRUPTED_TEXT);
|
|
1751
|
+
resultsById.set(
|
|
1752
|
+
toolCall.id,
|
|
1753
|
+
interruptible && preemptSignal?.aborted && !alreadyMarked ? markSteerInterrupted(record) : record
|
|
1754
|
+
);
|
|
1755
|
+
if (preemptSignal?.aborted && graceTimer === void 0 && running.size === 0) {
|
|
1756
|
+
eventStream.close();
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
preempted: () => preemptSignal?.aborted === true,
|
|
1760
|
+
dispose: () => {
|
|
1761
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
1762
|
+
preemptSignal?.removeEventListener("abort", onPreempt);
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1656
1766
|
async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
1657
1767
|
const eventStream = new import_gg_ai.EventStream();
|
|
1658
1768
|
const state = { finalized: false };
|
|
@@ -1660,6 +1770,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1660
1770
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1661
1771
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1662
1772
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1773
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1663
1774
|
const phases = [];
|
|
1664
1775
|
let currentParallel = [];
|
|
1665
1776
|
for (const toolCall of toolCalls) {
|
|
@@ -1680,40 +1791,18 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1680
1791
|
void (async () => {
|
|
1681
1792
|
try {
|
|
1682
1793
|
for (const phase of phases) {
|
|
1683
|
-
if (options.signal?.aborted) break;
|
|
1794
|
+
if (options.signal?.aborted || options.preemptSignal?.aborted) break;
|
|
1684
1795
|
if (phase.sequential) {
|
|
1685
1796
|
const signature = JSON.stringify([
|
|
1686
1797
|
phase.sequential.name,
|
|
1687
1798
|
canonicalToolArgs(phase.sequential.args)
|
|
1688
1799
|
]);
|
|
1689
1800
|
if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
|
|
1690
|
-
|
|
1691
|
-
const record = await executeSingleToolCall(
|
|
1692
|
-
phase.sequential,
|
|
1693
|
-
options,
|
|
1694
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1695
|
-
);
|
|
1696
|
-
resultsById.set(record.toolCallId, record);
|
|
1801
|
+
await tracker.run(phase.sequential);
|
|
1697
1802
|
} else if (phase.parallel.length === 1) {
|
|
1698
|
-
|
|
1699
|
-
const record = await executeSingleToolCall(
|
|
1700
|
-
phase.parallel[0],
|
|
1701
|
-
options,
|
|
1702
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1703
|
-
);
|
|
1704
|
-
resultsById.set(record.toolCallId, record);
|
|
1803
|
+
await tracker.run(phase.parallel[0]);
|
|
1705
1804
|
} else {
|
|
1706
|
-
await Promise.all(
|
|
1707
|
-
phase.parallel.map(async (toolCall) => {
|
|
1708
|
-
dispatchedIds.add(toolCall.id);
|
|
1709
|
-
const record = await executeSingleToolCall(
|
|
1710
|
-
toolCall,
|
|
1711
|
-
options,
|
|
1712
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1713
|
-
);
|
|
1714
|
-
resultsById.set(record.toolCallId, record);
|
|
1715
|
-
})
|
|
1716
|
-
);
|
|
1805
|
+
await Promise.all(phase.parallel.map((toolCall) => tracker.run(toolCall)));
|
|
1717
1806
|
}
|
|
1718
1807
|
}
|
|
1719
1808
|
if (!state.finalized) eventStream.close();
|
|
@@ -1734,12 +1823,13 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1734
1823
|
}
|
|
1735
1824
|
} finally {
|
|
1736
1825
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1826
|
+
tracker.dispose();
|
|
1737
1827
|
state.finalized = true;
|
|
1738
1828
|
}
|
|
1739
1829
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1740
1830
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1741
1831
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1742
|
-
return { toolResults, aborted };
|
|
1832
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1743
1833
|
}
|
|
1744
1834
|
async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
|
|
1745
1835
|
const eventStream = new import_gg_ai.EventStream();
|
|
@@ -1748,17 +1838,8 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1748
1838
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1749
1839
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1750
1840
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
dispatchedIds.add(toolCall.id);
|
|
1754
|
-
const record = await executeSingleToolCall(
|
|
1755
|
-
toolCall,
|
|
1756
|
-
options,
|
|
1757
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1758
|
-
);
|
|
1759
|
-
resultsById.set(record.toolCallId, record);
|
|
1760
|
-
})
|
|
1761
|
-
).then(() => {
|
|
1841
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1842
|
+
Promise.all(toolCalls.map((toolCall) => tracker.run(toolCall))).then(() => {
|
|
1762
1843
|
if (!state.finalized) eventStream.close();
|
|
1763
1844
|
}).catch((err) => {
|
|
1764
1845
|
if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1776,12 +1857,13 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1776
1857
|
}
|
|
1777
1858
|
} finally {
|
|
1778
1859
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1860
|
+
tracker.dispose();
|
|
1779
1861
|
state.finalized = true;
|
|
1780
1862
|
}
|
|
1781
1863
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1782
1864
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1783
1865
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1784
|
-
return { toolResults, aborted };
|
|
1866
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1785
1867
|
}
|
|
1786
1868
|
function cancelledBeforeStartText(name) {
|
|
1787
1869
|
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
@@ -2077,6 +2159,7 @@ var Agent = class {
|
|
|
2077
2159
|
options;
|
|
2078
2160
|
steeringQueue = [];
|
|
2079
2161
|
followUpQueue = [];
|
|
2162
|
+
steeringListeners = /* @__PURE__ */ new Set();
|
|
2080
2163
|
constructor(options) {
|
|
2081
2164
|
this.options = options;
|
|
2082
2165
|
if (options.system) {
|
|
@@ -2101,9 +2184,14 @@ var Agent = class {
|
|
|
2101
2184
|
get running() {
|
|
2102
2185
|
return this._running;
|
|
2103
2186
|
}
|
|
2104
|
-
/**
|
|
2187
|
+
/**
|
|
2188
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
2189
|
+
* interruptible ones are preempted right away and the message is injected
|
|
2190
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
2191
|
+
*/
|
|
2105
2192
|
steer(msg) {
|
|
2106
2193
|
this.steeringQueue.push(msg);
|
|
2194
|
+
for (const listener of [...this.steeringListeners]) listener();
|
|
2107
2195
|
}
|
|
2108
2196
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
2109
2197
|
followUp(msg) {
|
|
@@ -2123,6 +2211,14 @@ var Agent = class {
|
|
|
2123
2211
|
const all = [...callerResult ?? [], ...queued];
|
|
2124
2212
|
return all.length > 0 ? all : null;
|
|
2125
2213
|
},
|
|
2214
|
+
onSteeringAvailable: (listener) => {
|
|
2215
|
+
this.steeringListeners.add(listener);
|
|
2216
|
+
const unsubscribeCaller = this.options.onSteeringAvailable?.(listener);
|
|
2217
|
+
return () => {
|
|
2218
|
+
this.steeringListeners.delete(listener);
|
|
2219
|
+
unsubscribeCaller?.();
|
|
2220
|
+
};
|
|
2221
|
+
},
|
|
2126
2222
|
getFollowUpMessages: async () => {
|
|
2127
2223
|
const callerResult = await this.options.getFollowUpMessages?.() ?? [];
|
|
2128
2224
|
const queued = this.followUpQueue.splice(0);
|