@agentick/core 0.7.0 → 0.8.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/.tsbuildinfo.build +1 -1
- package/dist/agentick-instance.d.ts.map +1 -1
- package/dist/agentick-instance.js +28 -0
- package/dist/agentick-instance.js.map +1 -1
- package/dist/app/inbox-storage.d.ts +24 -0
- package/dist/app/inbox-storage.d.ts.map +1 -0
- package/dist/app/inbox-storage.js +99 -0
- package/dist/app/inbox-storage.js.map +1 -0
- package/dist/app/session.d.ts +23 -2
- package/dist/app/session.d.ts.map +1 -1
- package/dist/app/session.js +283 -26
- package/dist/app/session.js.map +1 -1
- package/dist/app/types.d.ts +97 -0
- package/dist/app/types.d.ts.map +1 -1
- package/dist/app.d.ts +2 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -0
- package/dist/app.js.map +1 -1
- package/dist/com/object-model.d.ts +9 -3
- package/dist/com/object-model.d.ts.map +1 -1
- package/dist/com/object-model.js +52 -24
- package/dist/com/object-model.js.map +1 -1
- package/dist/compiler/collector.d.ts.map +1 -1
- package/dist/compiler/collector.js +177 -8
- package/dist/compiler/collector.js.map +1 -1
- package/dist/engine/tool-executor.d.ts +15 -1
- package/dist/engine/tool-executor.d.ts.map +1 -1
- package/dist/engine/tool-executor.js +64 -6
- package/dist/engine/tool-executor.js.map +1 -1
- package/dist/hooks/expandable.d.ts +29 -2
- package/dist/hooks/expandable.d.ts.map +1 -1
- package/dist/hooks/expandable.js +28 -7
- package/dist/hooks/expandable.js.map +1 -1
- package/dist/hooks/gate.d.ts +27 -0
- package/dist/hooks/gate.d.ts.map +1 -0
- package/dist/hooks/gate.js +59 -0
- package/dist/hooks/gate.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/jsx/components/auto-summary.d.ts +31 -0
- package/dist/jsx/components/auto-summary.d.ts.map +1 -0
- package/dist/jsx/components/auto-summary.js +83 -0
- package/dist/jsx/components/auto-summary.js.map +1 -0
- package/dist/jsx/components/collapsed.d.ts +12 -2
- package/dist/jsx/components/collapsed.d.ts.map +1 -1
- package/dist/jsx/components/collapsed.js +16 -1
- package/dist/jsx/components/collapsed.js.map +1 -1
- package/dist/jsx/components/content.d.ts +26 -13
- package/dist/jsx/components/content.d.ts.map +1 -1
- package/dist/jsx/components/content.js +64 -15
- package/dist/jsx/components/content.js.map +1 -1
- package/dist/jsx/components/index.d.ts +1 -0
- package/dist/jsx/components/index.d.ts.map +1 -1
- package/dist/jsx/components/index.js +1 -0
- package/dist/jsx/components/index.js.map +1 -1
- package/dist/jsx/components/primitives.d.ts +34 -2
- package/dist/jsx/components/primitives.d.ts.map +1 -1
- package/dist/jsx/components/primitives.js +79 -21
- package/dist/jsx/components/primitives.js.map +1 -1
- package/dist/local-transport.d.ts.map +1 -1
- package/dist/local-transport.js +4 -0
- package/dist/local-transport.js.map +1 -1
- package/dist/model/adapter.d.ts +1 -1
- package/dist/model/adapter.d.ts.map +1 -1
- package/dist/model/adapter.js +7 -4
- package/dist/model/adapter.js.map +1 -1
- package/dist/model/model.d.ts +2 -0
- package/dist/model/model.d.ts.map +1 -1
- package/dist/renderers/base.d.ts +9 -0
- package/dist/renderers/base.d.ts.map +1 -1
- package/dist/renderers/base.js +31 -0
- package/dist/renderers/base.js.map +1 -1
- package/dist/renderers/markdown.d.ts.map +1 -1
- package/dist/renderers/markdown.js +21 -3
- package/dist/renderers/markdown.js.map +1 -1
- package/dist/renderers/xml.d.ts.map +1 -1
- package/dist/renderers/xml.js +9 -1
- package/dist/renderers/xml.js.map +1 -1
- package/dist/testing/mock-app.d.ts.map +1 -1
- package/dist/testing/mock-app.js +12 -0
- package/dist/testing/mock-app.js.map +1 -1
- package/dist/tool/tool.d.ts +10 -0
- package/dist/tool/tool.d.ts.map +1 -1
- package/dist/tool/tool.js +2 -0
- package/dist/tool/tool.js.map +1 -1
- package/dist/utils/classify-error.d.ts +1 -1
- package/dist/utils/classify-error.d.ts.map +1 -1
- package/dist/utils/classify-error.js +8 -0
- package/dist/utils/classify-error.js.map +1 -1
- package/package.json +3 -3
package/dist/app/session.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { EventEmitter } from "node:events";
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
|
-
import { Context, createProcedure, Channel, EventBuffer, ExecutionHandleBrand, Logger, } from "@agentick/kernel";
|
|
17
|
+
import { Context, createProcedure, Channel, EventBuffer, ExecutionHandleBrand, Logger, parseSchema, } from "@agentick/kernel";
|
|
18
18
|
import { FiberCompiler, StructureRenderer, ReconciliationScheduler, } from "../compiler";
|
|
19
19
|
import { COM } from "../com/object-model";
|
|
20
20
|
import { MarkdownRenderer } from "../renderers/index";
|
|
@@ -71,6 +71,7 @@ export class SessionImpl extends EventEmitter {
|
|
|
71
71
|
_tick = 1;
|
|
72
72
|
_isAborted = false;
|
|
73
73
|
_currentExecutionId = null;
|
|
74
|
+
_currentToolCallId = null;
|
|
74
75
|
// Compilation infrastructure (no intermediate layer)
|
|
75
76
|
compiler = null;
|
|
76
77
|
ctx = null;
|
|
@@ -117,8 +118,14 @@ export class SessionImpl extends EventEmitter {
|
|
|
117
118
|
sessionOptions;
|
|
118
119
|
// Last props for hot-update support
|
|
119
120
|
_lastProps = null;
|
|
121
|
+
_mountPromise = null;
|
|
120
122
|
// Captured context from session creation
|
|
121
123
|
_capturedContext;
|
|
124
|
+
// Inbox (durable external message delivery)
|
|
125
|
+
_inboxStorage = null;
|
|
126
|
+
_inboxUnsubscribe = null;
|
|
127
|
+
_draining = false;
|
|
128
|
+
_drainRequested = false;
|
|
122
129
|
// Channels for pub/sub communication
|
|
123
130
|
_channels = new Map();
|
|
124
131
|
// Current execution handle (for concurrent send idempotency)
|
|
@@ -238,6 +245,7 @@ export class SessionImpl extends EventEmitter {
|
|
|
238
245
|
send;
|
|
239
246
|
render;
|
|
240
247
|
spawn;
|
|
248
|
+
dispatch;
|
|
241
249
|
initProcedures() {
|
|
242
250
|
// Queue procedure - queues messages and notifies components
|
|
243
251
|
this.queue = createProcedure({
|
|
@@ -401,8 +409,15 @@ export class SessionImpl extends EventEmitter {
|
|
|
401
409
|
inheritDefaults: this.appOptions.inheritDefaults,
|
|
402
410
|
runner: spawnOptions?.runner ?? this.appOptions.runner,
|
|
403
411
|
};
|
|
412
|
+
// Child abort signal: merge session-level (parent dies → child dies)
|
|
413
|
+
// and execution-level (parent execution aborted → child aborted).
|
|
414
|
+
// Session signal always present; execution signal only during active tick.
|
|
415
|
+
const abortSignals = [this.sessionAbortController.signal];
|
|
416
|
+
if (this.executionAbortController) {
|
|
417
|
+
abortSignals.push(this.executionAbortController.signal);
|
|
418
|
+
}
|
|
404
419
|
const childOptions = {
|
|
405
|
-
signal:
|
|
420
|
+
signal: AbortSignal.any(abortSignals),
|
|
406
421
|
devTools: this.sessionOptions.devTools ?? this.appOptions.devTools,
|
|
407
422
|
};
|
|
408
423
|
const child = new SessionImpl(Component, childAppOptions, childOptions);
|
|
@@ -414,15 +429,108 @@ export class SessionImpl extends EventEmitter {
|
|
|
414
429
|
...resolvedInput,
|
|
415
430
|
props: mergedProps,
|
|
416
431
|
});
|
|
417
|
-
// 4.
|
|
418
|
-
|
|
432
|
+
// 4. Spawn lifecycle events & child event forwarding
|
|
433
|
+
const spawnId = randomUUID();
|
|
434
|
+
this.emitEvent({
|
|
435
|
+
type: "spawn_start",
|
|
436
|
+
spawnId,
|
|
437
|
+
parentExecutionId: this._currentExecutionId ?? "",
|
|
438
|
+
childExecutionId: handle.sessionId,
|
|
439
|
+
componentName: Component.displayName || Component.name,
|
|
440
|
+
label: spawnOptions?.label,
|
|
441
|
+
originCallId: this._currentToolCallId ?? undefined,
|
|
442
|
+
});
|
|
443
|
+
// Forward child events to parent's buffer, tagged with spawnPath.
|
|
444
|
+
// Recursive spawns work automatically: child events already have
|
|
445
|
+
// spawnPath from grandchildren, we prepend our spawnId.
|
|
446
|
+
const childCallIds = new Set();
|
|
447
|
+
// Forward child events, then emit spawn_end after forwarding completes.
|
|
448
|
+
// This guarantees spawn_end comes AFTER all forwarded child events —
|
|
449
|
+
// no interleaving of spawn_end with trailing content deltas.
|
|
450
|
+
const forwardingPromise = (async () => {
|
|
451
|
+
for await (const event of handle) {
|
|
452
|
+
if (event.type === "tool_confirmation_required") {
|
|
453
|
+
childCallIds.add(event.callId);
|
|
454
|
+
}
|
|
455
|
+
this.emitEvent({
|
|
456
|
+
...event,
|
|
457
|
+
spawnPath: [spawnId, ...(event.spawnPath ?? [])],
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
})();
|
|
461
|
+
// Prevent unhandled rejection if parent aborts mid-forward
|
|
462
|
+
forwardingPromise.catch(() => { });
|
|
463
|
+
// Route parent confirmation responses to child's channel
|
|
464
|
+
const parentConfirmChannel = this.channel("tool_confirmation");
|
|
465
|
+
const unsubConfirm = parentConfirmChannel.subscribe((event) => {
|
|
466
|
+
if (event.type === "response" && event.id && childCallIds.has(event.id)) {
|
|
467
|
+
child.channel("tool_confirmation").publish(event);
|
|
468
|
+
childCallIds.delete(event.id);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
// spawn_end after all child events are forwarded (not racing with them).
|
|
472
|
+
// We await forwardingPromise to ensure ordering, then use handle.result
|
|
473
|
+
// for the final output/error.
|
|
474
|
+
forwardingPromise
|
|
475
|
+
.then(() => handle.result)
|
|
476
|
+
.then((result) => {
|
|
477
|
+
this.emitEvent({
|
|
478
|
+
type: "spawn_end",
|
|
479
|
+
spawnId,
|
|
480
|
+
parentExecutionId: this._currentExecutionId ?? "",
|
|
481
|
+
childExecutionId: handle.sessionId,
|
|
482
|
+
output: result.response,
|
|
483
|
+
usage: result.usage,
|
|
484
|
+
});
|
|
485
|
+
})
|
|
486
|
+
.catch((error) => {
|
|
487
|
+
this.emitEvent({
|
|
488
|
+
type: "spawn_end",
|
|
489
|
+
spawnId,
|
|
490
|
+
parentExecutionId: this._currentExecutionId ?? "",
|
|
491
|
+
childExecutionId: handle.sessionId,
|
|
492
|
+
output: error?.message ?? "spawn failed",
|
|
493
|
+
isError: true,
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
// 5. Cleanup after spawn_end is emitted
|
|
497
|
+
forwardingPromise
|
|
498
|
+
.then(() => handle.result)
|
|
419
499
|
.finally(async () => {
|
|
500
|
+
unsubConfirm();
|
|
501
|
+
childCallIds.clear();
|
|
420
502
|
this._children = this._children.filter((c) => c !== child);
|
|
421
503
|
await child.close();
|
|
422
504
|
})
|
|
423
505
|
.catch(() => { });
|
|
424
506
|
return handle;
|
|
425
507
|
});
|
|
508
|
+
// Dispatch procedure - dispatches tools by name/alias from the user side
|
|
509
|
+
this.dispatch = createProcedure({
|
|
510
|
+
name: "session:dispatch",
|
|
511
|
+
metadata: { operation: "dispatch" },
|
|
512
|
+
handleFactory: false,
|
|
513
|
+
executionBoundary: false,
|
|
514
|
+
}, async (name, input) => {
|
|
515
|
+
if (this.isTerminal)
|
|
516
|
+
throw new Error(this.terminalError);
|
|
517
|
+
await this.mount();
|
|
518
|
+
const tool = this.ctx.getTool(name) ?? this.ctx.getToolByAlias(name);
|
|
519
|
+
if (!tool)
|
|
520
|
+
throw new Error(`Unknown command: ${name}`);
|
|
521
|
+
if (!tool.run)
|
|
522
|
+
throw new Error(`Command "${name}" has no handler`);
|
|
523
|
+
// Validate input against tool's schema
|
|
524
|
+
const validatedInput = tool.metadata.input
|
|
525
|
+
? await parseSchema(tool.metadata.input, input)
|
|
526
|
+
: input;
|
|
527
|
+
const result = await tool.run.exec(validatedInput).result;
|
|
528
|
+
if (Array.isArray(result))
|
|
529
|
+
return result;
|
|
530
|
+
if (typeof result === "string")
|
|
531
|
+
return [{ type: "text", text: result }];
|
|
532
|
+
throw new Error(`Unexpected tool result type: ${typeof result}. Expected ContentBlock[] or string.`);
|
|
533
|
+
});
|
|
426
534
|
}
|
|
427
535
|
// ════════════════════════════════════════════════════════════════════════
|
|
428
536
|
// SessionExecutionHandle Creation
|
|
@@ -690,6 +798,40 @@ export class SessionImpl extends EventEmitter {
|
|
|
690
798
|
clearAbort() {
|
|
691
799
|
this._isAborted = false;
|
|
692
800
|
}
|
|
801
|
+
async mount() {
|
|
802
|
+
// Order matters: _mountPromise must be checked FIRST.
|
|
803
|
+
// _doMount() sets this.compiler synchronously (via ensureCompilationInfrastructure)
|
|
804
|
+
// before tool registration completes. A concurrent caller seeing compiler=truthy
|
|
805
|
+
// would skip the mount and fail tool lookup.
|
|
806
|
+
if (this._mountPromise)
|
|
807
|
+
return this._mountPromise;
|
|
808
|
+
if (this.compiler)
|
|
809
|
+
return; // Mounted via render() or previous mount()
|
|
810
|
+
this._mountPromise = this._doMount();
|
|
811
|
+
try {
|
|
812
|
+
await this._mountPromise;
|
|
813
|
+
}
|
|
814
|
+
catch (e) {
|
|
815
|
+
this._mountPromise = null; // Allow retry on failure
|
|
816
|
+
throw e;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async _doMount() {
|
|
820
|
+
const rootElement = jsx(this.Component, this._lastProps ?? {});
|
|
821
|
+
await this.ensureCompilationInfrastructure(rootElement);
|
|
822
|
+
// Single compile pass — no notifyTickStart (no catch-up), no compileUntilStable (no afterCompile).
|
|
823
|
+
// This only renders the tree to collect tools. No tick lifecycle hooks fire.
|
|
824
|
+
const tickState = {
|
|
825
|
+
tick: 0,
|
|
826
|
+
queuedMessages: [],
|
|
827
|
+
timeline: [],
|
|
828
|
+
stop: () => { },
|
|
829
|
+
};
|
|
830
|
+
const compiled = await this.compiler.compile(rootElement, tickState);
|
|
831
|
+
// Register collected tools so they're available for dispatch
|
|
832
|
+
const mergedTools = this.mergeTools(this.appOptions.tools ?? [], this.sessionOptions.tools ?? [], [], compiled.tools);
|
|
833
|
+
await Promise.all(mergedTools.map((tool) => this.ctx.addTool(tool)));
|
|
834
|
+
}
|
|
693
835
|
startExecutionAbort(signal) {
|
|
694
836
|
this.executionAbortCleanup.forEach((cleanup) => cleanup());
|
|
695
837
|
this.executionAbortCleanup = [];
|
|
@@ -778,16 +920,22 @@ export class SessionImpl extends EventEmitter {
|
|
|
778
920
|
devToolsEnabled: sessionCtx.devToolsEnabled ?? this.sessionOptions.devTools ?? false,
|
|
779
921
|
});
|
|
780
922
|
// Invoke lifecycle callbacks
|
|
923
|
+
// Forwarded child events (with spawnPath) only trigger onEvent, not
|
|
924
|
+
// lifecycle-specific callbacks like onTickStart/onTickEnd — those
|
|
925
|
+
// semantics belong to the parent's own tick lifecycle.
|
|
781
926
|
const cb = this.callbacks;
|
|
927
|
+
const isForwarded = enrichedEvent.spawnPath?.length > 0;
|
|
782
928
|
try {
|
|
783
929
|
cb.onEvent?.(enrichedEvent);
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
930
|
+
if (!isForwarded) {
|
|
931
|
+
// Call specific callbacks based on event type
|
|
932
|
+
const eventType = enrichedEvent.type;
|
|
933
|
+
if (eventType === "tick_start") {
|
|
934
|
+
cb.onTickStart?.(enrichedEvent.tick, enrichedEvent.executionId);
|
|
935
|
+
}
|
|
936
|
+
else if (eventType === "tick_end") {
|
|
937
|
+
cb.onTickEnd?.(enrichedEvent.tick, enrichedEvent.usage);
|
|
938
|
+
}
|
|
791
939
|
}
|
|
792
940
|
}
|
|
793
941
|
catch {
|
|
@@ -829,6 +977,78 @@ export class SessionImpl extends EventEmitter {
|
|
|
829
977
|
setPersistCallback(callback) {
|
|
830
978
|
this._persistCallback = callback;
|
|
831
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* Connect this session to an inbox storage backend.
|
|
982
|
+
* Subscribes to notifications and drains any pre-existing pending messages.
|
|
983
|
+
* @internal
|
|
984
|
+
*/
|
|
985
|
+
setInboxStorage(storage) {
|
|
986
|
+
this._inboxStorage = storage;
|
|
987
|
+
this._inboxUnsubscribe = storage.subscribe(this.id, () => {
|
|
988
|
+
this.drainInbox().catch((err) => {
|
|
989
|
+
this.log.warn({ error: err }, "Inbox drain failed");
|
|
990
|
+
});
|
|
991
|
+
});
|
|
992
|
+
// Drain pre-existing pending messages
|
|
993
|
+
this.drainInbox().catch((err) => {
|
|
994
|
+
this.log.warn({ error: err }, "Initial inbox drain failed");
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Process all pending inbox messages. Called by App.processInbox().
|
|
999
|
+
* @internal
|
|
1000
|
+
*/
|
|
1001
|
+
async processInboxMessages() {
|
|
1002
|
+
await this.drainInbox();
|
|
1003
|
+
}
|
|
1004
|
+
async drainInbox() {
|
|
1005
|
+
const storage = this._inboxStorage;
|
|
1006
|
+
if (this.isTerminal || !storage)
|
|
1007
|
+
return;
|
|
1008
|
+
if (this._draining) {
|
|
1009
|
+
// Signal that new messages arrived; current drain will re-check on exit
|
|
1010
|
+
this._drainRequested = true;
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
this._draining = true;
|
|
1014
|
+
try {
|
|
1015
|
+
do {
|
|
1016
|
+
this._drainRequested = false;
|
|
1017
|
+
const messages = await storage.pending(this.id);
|
|
1018
|
+
for (const msg of messages) {
|
|
1019
|
+
if (this.isTerminal)
|
|
1020
|
+
return;
|
|
1021
|
+
try {
|
|
1022
|
+
if (msg.type === "message") {
|
|
1023
|
+
const handle = await this.send({ messages: [msg.payload] });
|
|
1024
|
+
await handle.result;
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
await this.dispatch.exec(msg.payload.tool, msg.payload.input).result;
|
|
1028
|
+
}
|
|
1029
|
+
if (this.isTerminal)
|
|
1030
|
+
return;
|
|
1031
|
+
await storage.markDone(this.id, msg.id);
|
|
1032
|
+
}
|
|
1033
|
+
catch (err) {
|
|
1034
|
+
this.log.warn({ error: err, messageId: msg.id }, "Inbox message processing failed");
|
|
1035
|
+
break; // Preserve FIFO — stop on first failure
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
} while (this._drainRequested && !this.isTerminal);
|
|
1039
|
+
}
|
|
1040
|
+
finally {
|
|
1041
|
+
this._draining = false;
|
|
1042
|
+
// Do NOT clear _drainRequested here — a notification may have arrived
|
|
1043
|
+
// between the while-check and finally. If so, re-enter.
|
|
1044
|
+
if (this._drainRequested && !this.isTerminal) {
|
|
1045
|
+
this._drainRequested = false;
|
|
1046
|
+
this.drainInbox().catch((err) => {
|
|
1047
|
+
this.log.warn({ error: err }, "Inbox re-drain failed");
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
832
1052
|
/**
|
|
833
1053
|
* Set a snapshot to be applied/resolved when compilation infrastructure is created.
|
|
834
1054
|
* @internal
|
|
@@ -1222,6 +1442,10 @@ export class SessionImpl extends EventEmitter {
|
|
|
1222
1442
|
if (this.isTerminal)
|
|
1223
1443
|
return;
|
|
1224
1444
|
this._status = "closed";
|
|
1445
|
+
// Unsubscribe from inbox before anything else
|
|
1446
|
+
this._inboxUnsubscribe?.();
|
|
1447
|
+
this._inboxUnsubscribe = null;
|
|
1448
|
+
this._inboxStorage = null;
|
|
1225
1449
|
// Notify execution runner of destroy
|
|
1226
1450
|
if (this._runnerInitialized && this.appOptions.runner?.onDestroy) {
|
|
1227
1451
|
try {
|
|
@@ -1474,12 +1698,25 @@ export class SessionImpl extends EventEmitter {
|
|
|
1474
1698
|
}
|
|
1475
1699
|
// Stream model output if supported
|
|
1476
1700
|
let modelOutput;
|
|
1701
|
+
let streamed = false;
|
|
1477
1702
|
if (model.stream) {
|
|
1703
|
+
streamed = true;
|
|
1478
1704
|
const streamIterable = await model.stream(modelInput);
|
|
1479
1705
|
for await (const event of streamIterable) {
|
|
1480
1706
|
if (signal.aborted) {
|
|
1481
1707
|
throw new AbortError("Execution aborted", signal.reason);
|
|
1482
1708
|
}
|
|
1709
|
+
// Enrich tool_call with displaySummary — the stream accumulator
|
|
1710
|
+
// doesn't have access to tool definitions, so we add it here.
|
|
1711
|
+
if (event.type === "tool_call" && "name" in event && compiled.tools) {
|
|
1712
|
+
const toolDef = compiled.tools.find((t) => t.metadata?.name === event.name);
|
|
1713
|
+
if (toolDef) {
|
|
1714
|
+
const summary = tryDisplaySummary(toolDef, event.input);
|
|
1715
|
+
if (summary) {
|
|
1716
|
+
event.summary = summary;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1483
1720
|
this.emitEvent(event);
|
|
1484
1721
|
if (event.type === "message" && "message" in event) {
|
|
1485
1722
|
const messageEvent = event;
|
|
@@ -1561,20 +1798,24 @@ export class SessionImpl extends EventEmitter {
|
|
|
1561
1798
|
let toolStartTime;
|
|
1562
1799
|
if (response.toolCalls?.length && this.ctx) {
|
|
1563
1800
|
toolStartTime = Date.now();
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1801
|
+
// Only emit tool_call events for non-streaming path.
|
|
1802
|
+
// The streaming path already emitted these via stream accumulator.
|
|
1803
|
+
if (!streamed) {
|
|
1804
|
+
for (const call of response.toolCalls) {
|
|
1805
|
+
const toolCallTimestamp = timestamp();
|
|
1806
|
+
const toolDef = compiled.tools?.find((t) => t.metadata?.name === call.name);
|
|
1807
|
+
const summary = tryDisplaySummary(toolDef, call.input);
|
|
1808
|
+
this.emitEvent({
|
|
1809
|
+
type: "tool_call",
|
|
1810
|
+
callId: call.id,
|
|
1811
|
+
blockIndex: 0,
|
|
1812
|
+
name: call.name,
|
|
1813
|
+
input: call.input,
|
|
1814
|
+
summary,
|
|
1815
|
+
startedAt: toolCallTimestamp,
|
|
1816
|
+
completedAt: toolCallTimestamp,
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1578
1819
|
}
|
|
1579
1820
|
toolResults = await this.executeTools(toolExecutor, response.toolCalls, compiled.tools, outputs, currentTick, timestamp);
|
|
1580
1821
|
}
|
|
@@ -1807,7 +2048,7 @@ export class SessionImpl extends EventEmitter {
|
|
|
1807
2048
|
this.scheduler.schedule(reason ?? "COM recompile request");
|
|
1808
2049
|
});
|
|
1809
2050
|
// Wire COM spawn delegate to session's spawn Procedure
|
|
1810
|
-
this.ctx.setSpawnCallback((agent, input) => this.spawn(agent, input));
|
|
2051
|
+
this.ctx.setSpawnCallback((agent, input, options) => this.spawn(agent, input, options));
|
|
1811
2052
|
this.structureRenderer = new StructureRenderer(this.ctx);
|
|
1812
2053
|
this.structureRenderer.setDefaultRenderer(new MarkdownRenderer());
|
|
1813
2054
|
// Tools are registered in compileTick() after merging all sources
|
|
@@ -2009,6 +2250,11 @@ export class SessionImpl extends EventEmitter {
|
|
|
2009
2250
|
}
|
|
2010
2251
|
}
|
|
2011
2252
|
});
|
|
2253
|
+
// Cancel pending confirmations when execution is aborted (e.g. Ctrl+C).
|
|
2254
|
+
// Without this, waitForConfirmation() hangs forever on abort.
|
|
2255
|
+
const abortSignal = this.executionAbortController?.signal;
|
|
2256
|
+
const onAbort = () => coordinator.cancelAll();
|
|
2257
|
+
abortSignal?.addEventListener("abort", onAbort);
|
|
2012
2258
|
// Confirmation callbacks for stream event emission
|
|
2013
2259
|
const confirmationCallbacks = {
|
|
2014
2260
|
onConfirmationRequired: async (call, message, metadata) => {
|
|
@@ -2033,7 +2279,16 @@ export class SessionImpl extends EventEmitter {
|
|
|
2033
2279
|
};
|
|
2034
2280
|
try {
|
|
2035
2281
|
for (const call of toolCalls) {
|
|
2282
|
+
if (abortSignal?.aborted)
|
|
2283
|
+
break;
|
|
2036
2284
|
const startedAt = timestamp();
|
|
2285
|
+
this._currentToolCallId = call.id;
|
|
2286
|
+
// Signal tool execution beginning (fills the gap between tool_call and tool_result)
|
|
2287
|
+
this.emitEvent({
|
|
2288
|
+
type: "tool_result_start",
|
|
2289
|
+
callId: call.id,
|
|
2290
|
+
name: call.name,
|
|
2291
|
+
});
|
|
2037
2292
|
// Check if OUTPUT tool
|
|
2038
2293
|
const tool = executableTools.find((t) => t.metadata?.name === call.name);
|
|
2039
2294
|
const isOutputTool = tool && tool.metadata?.type === "output";
|
|
@@ -2108,6 +2363,8 @@ export class SessionImpl extends EventEmitter {
|
|
|
2108
2363
|
}
|
|
2109
2364
|
}
|
|
2110
2365
|
finally {
|
|
2366
|
+
this._currentToolCallId = null;
|
|
2367
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
2111
2368
|
unsubscribe();
|
|
2112
2369
|
}
|
|
2113
2370
|
return results;
|