@code-yeongyu/senpi-agent-core 2026.10.25-4 → 2026.10.26-2
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/agent-loop.js +39 -13
- package/dist/agent.d.ts +4 -0
- package/dist/agent.js +7 -0
- package/dist/empty-assistant-recovery.js +26 -19
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal-events.d.ts +34 -0
- package/dist/internal-events.js +44 -0
- package/package.json +3 -3
package/dist/agent-loop.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Agent loop that works with AgentMessage throughout.
|
|
3
3
|
* Transforms to Message[] only at the LLM call boundary.
|
|
4
4
|
*/
|
|
5
|
-
import { createInitialSystemMessage, EventStream, getCurrentTools, getToolStateChanges, isCursorExecResolved, normalizeContext, supportsAllowedToolChoice, validateToolArguments, } from "@earendil-works/pi-ai";
|
|
5
|
+
import { assistantRecords, carryAssistantCapture, getAssistantCapture, createInitialSystemMessage, EventStream, getCurrentTools, getToolStateChanges, isCursorExecResolved, isAssistantProgress, normalizeContext, supportsAllowedToolChoice, validateToolArguments, } from "@earendil-works/pi-ai";
|
|
6
6
|
import { createTerminalFailureAssistantMessage, isStreamIdleTimeoutError, normalizeTerminalAssistantMessage, settleStopReason, shouldFinalizeIdleAsStop, shouldTerminateAssistantTurn, } from "./assistant-terminal-state.js";
|
|
7
7
|
import { createResponseTimingRecorder } from "./response-timing.js";
|
|
8
|
+
import { internalProgressSink } from "./internal-events.js";
|
|
8
9
|
import { getDefaultStreamFn, withEmptyAssistantRecovery } from "./stream-fn.js";
|
|
9
10
|
import { prepareAgentToolCallArguments } from "./tool-arguments.js";
|
|
10
11
|
import { resolveCallTool, withToolNameCorrection } from "./tool-name-alias.js";
|
|
@@ -510,7 +511,10 @@ async function streamAssistantResponse(context, config, signal, emit, streamFunc
|
|
|
510
511
|
});
|
|
511
512
|
// Record the requested level, whichever stream function answered.
|
|
512
513
|
const result = async () => Object.assign(await response.result(), { thinkingLevel: config.reasoning ?? "off" });
|
|
513
|
-
|
|
514
|
+
// Without an internal listener the loop reads the public stream exactly as before: no records, no carriers.
|
|
515
|
+
const sink = config[internalProgressSink];
|
|
516
|
+
const records = sink ? assistantRecords(response) : response;
|
|
517
|
+
const iterator = records[Symbol.asyncIterator]();
|
|
514
518
|
const eventReader = createAssistantEventReader(iterator, streamIdleTimeoutMs, requestAbortController.signal, (error) => requestAbortController.abort(error), preStream.remainingStartMs(), response, normalizeTimeoutMs(config.streamStartTimeoutMs));
|
|
515
519
|
try {
|
|
516
520
|
while (true) {
|
|
@@ -518,16 +522,20 @@ async function streamAssistantResponse(context, config, signal, emit, streamFunc
|
|
|
518
522
|
if (next.done)
|
|
519
523
|
break;
|
|
520
524
|
const event = next.value;
|
|
525
|
+
if (isAssistantProgress(event)) {
|
|
526
|
+
await sink?.(event);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
521
529
|
switch (event.type) {
|
|
522
530
|
case "start":
|
|
523
531
|
responseTiming.started();
|
|
524
532
|
partialMessage = event.partial;
|
|
525
533
|
context.messages.push(partialMessage);
|
|
526
534
|
addedPartial = true;
|
|
527
|
-
await emit({
|
|
535
|
+
await emit(carryAssistantCapture(event, {
|
|
528
536
|
type: "message_start",
|
|
529
537
|
message: { ...partialMessage },
|
|
530
|
-
});
|
|
538
|
+
}));
|
|
531
539
|
break;
|
|
532
540
|
case "text_start":
|
|
533
541
|
case "text_delta":
|
|
@@ -541,6 +549,7 @@ async function streamAssistantResponse(context, config, signal, emit, streamFunc
|
|
|
541
549
|
if (partialMessage) {
|
|
542
550
|
partialMessage = event.partial;
|
|
543
551
|
responseTiming.observe(event);
|
|
552
|
+
let timingProgress;
|
|
544
553
|
if (event.type === "thinking_start" ||
|
|
545
554
|
event.type === "thinking_delta" ||
|
|
546
555
|
event.type === "thinking_end") {
|
|
@@ -556,14 +565,23 @@ async function streamAssistantResponse(context, config, signal, emit, streamFunc
|
|
|
556
565
|
block.startedAt = timing.startedAt;
|
|
557
566
|
if (timing.endedAt !== undefined)
|
|
558
567
|
block.endedAt = timing.endedAt;
|
|
568
|
+
// Stamped after provider capture: it amends this event's seed or end block, so it follows it.
|
|
569
|
+
if (getAssistantCapture(event) && event.type !== "thinking_delta")
|
|
570
|
+
timingProgress = Object.freeze({
|
|
571
|
+
type: "message_block_metadata",
|
|
572
|
+
contentIndex: event.contentIndex,
|
|
573
|
+
...timing,
|
|
574
|
+
});
|
|
559
575
|
}
|
|
560
576
|
}
|
|
561
577
|
context.messages[context.messages.length - 1] = partialMessage;
|
|
562
|
-
await emit({
|
|
578
|
+
await emit(carryAssistantCapture(event, {
|
|
563
579
|
type: "message_update",
|
|
564
580
|
assistantMessageEvent: event,
|
|
565
581
|
message: { ...partialMessage },
|
|
566
|
-
});
|
|
582
|
+
}));
|
|
583
|
+
if (timingProgress)
|
|
584
|
+
await sink?.(timingProgress);
|
|
567
585
|
}
|
|
568
586
|
break;
|
|
569
587
|
case "done":
|
|
@@ -577,12 +595,12 @@ async function streamAssistantResponse(context, config, signal, emit, streamFunc
|
|
|
577
595
|
context.messages.push(finalMessage);
|
|
578
596
|
}
|
|
579
597
|
if (!addedPartial) {
|
|
580
|
-
await emit({
|
|
598
|
+
await emit(carryAssistantCapture(event, {
|
|
581
599
|
type: "message_start",
|
|
582
600
|
message: { ...finalMessage },
|
|
583
|
-
});
|
|
601
|
+
}, finalMessage));
|
|
584
602
|
}
|
|
585
|
-
await emit({ type: "message_end", message: finalMessage });
|
|
603
|
+
await emit(carryAssistantCapture(event, { type: "message_end", message: finalMessage }, finalMessage));
|
|
586
604
|
return { message: finalMessage, providerToolResults };
|
|
587
605
|
}
|
|
588
606
|
}
|
|
@@ -749,6 +767,8 @@ configuredStartTimeoutMs) {
|
|
|
749
767
|
const startTimeoutMs = normalizeTimeoutMs(streamStartTimeoutMs);
|
|
750
768
|
const reportedStartTimeoutMs = configuredStartTimeoutMs ?? startTimeoutMs;
|
|
751
769
|
let sawFirstEvent = false;
|
|
770
|
+
// Provider silence since the last public event: internal progress records re-arm neither bound.
|
|
771
|
+
let silentMs = 0;
|
|
752
772
|
let removeAbortListener;
|
|
753
773
|
let abortPromise;
|
|
754
774
|
if (signal !== undefined) {
|
|
@@ -775,10 +795,16 @@ configuredStartTimeoutMs) {
|
|
|
775
795
|
const readTimeoutMs = useStartBound ? startTimeoutMs : idleTimeoutMs;
|
|
776
796
|
const makeTimeoutError = useStartBound
|
|
777
797
|
? (ms) => new StreamStartTimeoutError(reportedStartTimeoutMs ?? ms)
|
|
778
|
-
: (ms) => new StreamIdleTimeoutError(ms);
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
798
|
+
: (ms) => new StreamIdleTimeoutError(idleTimeoutMs ?? ms);
|
|
799
|
+
const readStartedAt = Date.now();
|
|
800
|
+
const result = await readNextAssistantEvent(iterator, readTimeoutMs === undefined ? undefined : Math.max(1, readTimeoutMs - silentMs), makeTimeoutError, abortPromise, onIdleTimeout, stream, signal);
|
|
801
|
+
if (!result.done && isAssistantProgress(result.value))
|
|
802
|
+
silentMs += Date.now() - readStartedAt;
|
|
803
|
+
else {
|
|
804
|
+
silentMs = 0;
|
|
805
|
+
if (!result.done)
|
|
806
|
+
sawFirstEvent = true;
|
|
807
|
+
}
|
|
782
808
|
return result;
|
|
783
809
|
},
|
|
784
810
|
dispose: () => removeAbortListener?.(),
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ImageContent, type Message, type SimpleStreamOptions, type ThinkingBudgets, type TranscriptContext, type Transport } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type InternalAgentChannel } from "./internal-events.ts";
|
|
2
3
|
import type { AfterToolCallContext, AfterToolCallResult, AgentContext, AgentEvent, AgentLoopConfig, AgentLoopTurnUpdate, AgentMessage, AgentState, BeforeToolCallContext, BeforeToolCallResult, FinishTurn, PrepareNextTurnContext, PrepareRequest, QueueMode, StreamFn, ToolExecutionMode } from "./types.ts";
|
|
3
4
|
export type { QueueMode } from "./types.ts";
|
|
4
5
|
/**
|
|
@@ -8,6 +9,8 @@ export type { QueueMode } from "./types.ts";
|
|
|
8
9
|
export type AgentInitialState = Partial<Omit<AgentState, "pendingToolCalls" | "isStreaming" | "streamingMessage" | "errorMessage" | "providerDiagnostic">>;
|
|
9
10
|
/** Options for constructing an {@link Agent}. */
|
|
10
11
|
export interface AgentOptions {
|
|
12
|
+
/** @internal Construction-time channel for senpi's session layer only. */
|
|
13
|
+
internalChannel?: InternalAgentChannel;
|
|
11
14
|
initialState?: AgentInitialState;
|
|
12
15
|
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
13
16
|
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
@@ -74,6 +77,7 @@ export interface AgentContinuationOptions {
|
|
|
74
77
|
* and exposes queueing APIs for steering and follow-up messages.
|
|
75
78
|
*/
|
|
76
79
|
export declare class Agent {
|
|
80
|
+
#private;
|
|
77
81
|
private _state;
|
|
78
82
|
private readonly listeners;
|
|
79
83
|
private readonly steeringQueue;
|
package/dist/agent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readProviderDiagnostic, sanitizeProviderDiagnostic, } from "@earendil-works/pi-ai";
|
|
2
2
|
import { buildProviderContext as buildProviderContextFromAgentContext, runAgentLoop, runAgentLoopContinue, } from "./agent-loop.js";
|
|
3
3
|
import { ProviderRetryWatchdogAbortError } from "./assistant-terminal-state.js";
|
|
4
|
+
import { dispatchInternalAgent, internalLoopSink, } from "./internal-events.js";
|
|
4
5
|
import { isLocalFailure, localFailurePort } from "./local-failure.js";
|
|
5
6
|
import { getDefaultStreamFn } from "./stream-fn.js";
|
|
6
7
|
function defaultConvertToLlm(messages) {
|
|
@@ -131,6 +132,7 @@ class PendingMessageQueue {
|
|
|
131
132
|
* and exposes queueing APIs for steering and follow-up messages.
|
|
132
133
|
*/
|
|
133
134
|
export class Agent {
|
|
135
|
+
#internalChannel;
|
|
134
136
|
async buildProviderContext(context, signal) {
|
|
135
137
|
return buildProviderContextFromAgentContext(context, { convertToLlm: this.convertToLlm, transformContext: this.transformContext, model: this._state.model }, signal);
|
|
136
138
|
}
|
|
@@ -138,6 +140,7 @@ export class Agent {
|
|
|
138
140
|
this.listeners = new Set();
|
|
139
141
|
// Older compiled consumers may omit options or streamFn even though the current API requires them.
|
|
140
142
|
const runtimeOptions = options ?? {};
|
|
143
|
+
this.#internalChannel = runtimeOptions.internalChannel;
|
|
141
144
|
this._state = createMutableAgentState(runtimeOptions.initialState);
|
|
142
145
|
this.convertToLlm = runtimeOptions.convertToLlm ?? defaultConvertToLlm;
|
|
143
146
|
this.transformContext = runtimeOptions.transformContext;
|
|
@@ -458,6 +461,7 @@ export class Agent {
|
|
|
458
461
|
const giveBack = (queue, messages) => this.giveBack(queue, messages, queue === "steering" ? steeringQueueGeneration : followUpQueueGeneration);
|
|
459
462
|
const reasoning = this._state.reasoningBaseline ?? this._state.thinkingLevel;
|
|
460
463
|
return {
|
|
464
|
+
...internalLoopSink(this.#internalChannel),
|
|
461
465
|
model: this._state.model,
|
|
462
466
|
reasoning: reasoning === "off" ? undefined : reasoning,
|
|
463
467
|
thinkingSelection: this._state.thinkingSelection,
|
|
@@ -653,6 +657,9 @@ export class Agent {
|
|
|
653
657
|
if (!signal) {
|
|
654
658
|
throw new Error("Agent listener invoked outside active run");
|
|
655
659
|
}
|
|
660
|
+
const internal = dispatchInternalAgent(this.#internalChannel, { event });
|
|
661
|
+
if (internal)
|
|
662
|
+
await internal;
|
|
656
663
|
for (const listener of this.listeners) {
|
|
657
664
|
await listener(event, signal);
|
|
658
665
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAssistantMessageEventStream, EMPTY_RESPONSE_ERROR, EMPTY_TOOL_USE_ERROR, FORWARDED_EMPTY_RESPONSE_ERROR, FORWARDED_EMPTY_TOOL_USE_ERROR, getToolCallFormat, hasKimiTextToolCallRecovery, hasVisibleAssistantContent, hasVisibleText, shouldRecoverTextToolCalls, } from "@earendil-works/pi-ai";
|
|
1
|
+
import { assistantEvents, carryAssistantCapture, createAssistantMessageEventStream, EMPTY_RESPONSE_ERROR, EMPTY_TOOL_USE_ERROR, FORWARDED_EMPTY_RESPONSE_ERROR, FORWARDED_EMPTY_TOOL_USE_ERROR, getToolCallFormat, hasKimiTextToolCallRecovery, hasVisibleAssistantContent, hasVisibleText, pushAssistantProgress, shouldRecoverTextToolCalls, } from "@earendil-works/pi-ai";
|
|
2
2
|
function isEmptyStop(message) {
|
|
3
3
|
return message.stopReason === "stop" && !hasVisibleAssistantContent(message);
|
|
4
4
|
}
|
|
@@ -56,24 +56,34 @@ function createForwardedEmptyFailure(message, toolUse) {
|
|
|
56
56
|
}
|
|
57
57
|
function createRetryingStream(firstStream, createStream, policy) {
|
|
58
58
|
const outerStream = createAssistantMessageEventStream();
|
|
59
|
+
const forward = (entry) => {
|
|
60
|
+
if ("event" in entry)
|
|
61
|
+
outerStream.push(entry.event);
|
|
62
|
+
else
|
|
63
|
+
pushAssistantProgress(outerStream, entry.progress);
|
|
64
|
+
};
|
|
65
|
+
const replaced = (source, error) => carryAssistantCapture(source, { type: "error", reason: "error", error }, error);
|
|
59
66
|
void (async () => {
|
|
60
67
|
try {
|
|
61
68
|
let stream = firstStream;
|
|
62
69
|
let retrying = false;
|
|
63
70
|
for (;;) {
|
|
71
|
+
// Controls are held beside the events they follow, so a discarded attempt discards both.
|
|
64
72
|
const buffered = [];
|
|
65
73
|
let forwarding = false;
|
|
66
74
|
let retry = false;
|
|
67
|
-
|
|
75
|
+
const events = assistantEvents(stream, (progress) => {
|
|
76
|
+
if (forwarding)
|
|
77
|
+
pushAssistantProgress(outerStream, progress);
|
|
78
|
+
else
|
|
79
|
+
buffered.push({ progress });
|
|
80
|
+
});
|
|
81
|
+
for await (const event of events) {
|
|
68
82
|
if (event.type === "done") {
|
|
69
83
|
const emptyToolUse = isEmptyToolUse(event.message);
|
|
70
84
|
if (isEmptyStop(event.message) || emptyToolUse) {
|
|
71
85
|
if (forwarding) {
|
|
72
|
-
outerStream.push(
|
|
73
|
-
type: "error",
|
|
74
|
-
reason: "error",
|
|
75
|
-
error: createForwardedEmptyFailure(event.message, emptyToolUse),
|
|
76
|
-
});
|
|
86
|
+
outerStream.push(replaced(event, createForwardedEmptyFailure(event.message, emptyToolUse)));
|
|
77
87
|
outerStream.end();
|
|
78
88
|
return;
|
|
79
89
|
}
|
|
@@ -81,20 +91,17 @@ function createRetryingStream(firstStream, createStream, policy) {
|
|
|
81
91
|
retry = true;
|
|
82
92
|
break;
|
|
83
93
|
}
|
|
84
|
-
|
|
85
|
-
outerStream.push({ type: "error", reason: "error", error });
|
|
94
|
+
outerStream.push(replaced(event, createEmptyResponseFailure(event.message, emptyToolUse)));
|
|
86
95
|
outerStream.end();
|
|
87
96
|
return;
|
|
88
97
|
}
|
|
89
|
-
const
|
|
90
|
-
?
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
: event;
|
|
98
|
+
const message = retrying
|
|
99
|
+
? appendRetryDiagnostic(event.message, recoveryDiagnosticType(isEmptyToolUse(event.message)))
|
|
100
|
+
: event.message;
|
|
101
|
+
const terminal = retrying ? carryAssistantCapture(event, { ...event, message }, message) : event;
|
|
95
102
|
if (!forwarding) {
|
|
96
103
|
for (const pending of buffered)
|
|
97
|
-
|
|
104
|
+
forward(pending);
|
|
98
105
|
}
|
|
99
106
|
outerStream.push(terminal);
|
|
100
107
|
outerStream.end();
|
|
@@ -103,7 +110,7 @@ function createRetryingStream(firstStream, createStream, policy) {
|
|
|
103
110
|
if (event.type === "error") {
|
|
104
111
|
if (!forwarding) {
|
|
105
112
|
for (const pending of buffered)
|
|
106
|
-
|
|
113
|
+
forward(pending);
|
|
107
114
|
}
|
|
108
115
|
outerStream.push(event);
|
|
109
116
|
outerStream.end();
|
|
@@ -113,10 +120,10 @@ function createRetryingStream(firstStream, createStream, policy) {
|
|
|
113
120
|
outerStream.push(event);
|
|
114
121
|
continue;
|
|
115
122
|
}
|
|
116
|
-
buffered.push(event);
|
|
123
|
+
buffered.push({ event });
|
|
117
124
|
if (isMeaningfulContentEvent(event, policy)) {
|
|
118
125
|
for (const pending of buffered)
|
|
119
|
-
|
|
126
|
+
forward(pending);
|
|
120
127
|
forwarding = true;
|
|
121
128
|
}
|
|
122
129
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { AttributeValue, ExactTelemetryAttributes, InferEventAttributes, In
|
|
|
3
3
|
export { createTypedSpanStarter, defineTelemetrySchema, InMemoryTelemetryContext, NOOP_TELEMETRY_CONTEXT, } from "@earendil-works/pi-telemetry";
|
|
4
4
|
export * from "./agent.ts";
|
|
5
5
|
export * from "./agent-loop.ts";
|
|
6
|
+
export { createInternalAgentChannel, type InternalAgentChannel, type OrderedAgentRecord } from "./internal-events.ts";
|
|
6
7
|
export { EMPTY_TOOL_USE_DEMOTION_DIAGNOSTIC, ProviderRetryWatchdogAbortError } from "./assistant-terminal-state.ts";
|
|
7
8
|
export * from "./harness/agent-harness.ts";
|
|
8
9
|
export { type BranchPreparation, type BranchSummaryDetails, type BranchSummaryResult, type CollectEntriesResult, collectEntriesForBranchSummary, type FileOperations, type GenerateBranchSummaryOptions, generateBranchSummary, prepareBranchEntries, } from "./harness/compaction/branch-summarization.ts";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { uuidv7 } from "@earendil-works/pi-ai";
|
|
|
2
2
|
export { createTypedSpanStarter, defineTelemetrySchema, InMemoryTelemetryContext, NOOP_TELEMETRY_CONTEXT, } from "@earendil-works/pi-telemetry";
|
|
3
3
|
export * from "./agent.js";
|
|
4
4
|
export * from "./agent-loop.js";
|
|
5
|
+
export { createInternalAgentChannel } from "./internal-events.js";
|
|
5
6
|
export { EMPTY_TOOL_USE_DEMOTION_DIAGNOSTIC, ProviderRetryWatchdogAbortError } from "./assistant-terminal-state.js";
|
|
6
7
|
export * from "./harness/agent-harness.js";
|
|
7
8
|
export { collectEntriesForBranchSummary, generateBranchSummary, prepareBranchEntries, } from "./harness/compaction/branch-summarization.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type AssistantProgress } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { AgentEvent, AgentLoopConfig } from "./types.ts";
|
|
3
|
+
export declare const internalProgressSink: unique symbol;
|
|
4
|
+
type ProgressSink = (progress: AssistantProgress) => Promise<void> | undefined;
|
|
5
|
+
export type InternalLoopConfig = AgentLoopConfig & {
|
|
6
|
+
[internalProgressSink]?: ProgressSink;
|
|
7
|
+
};
|
|
8
|
+
type InternalEvent = {
|
|
9
|
+
event: AgentEvent;
|
|
10
|
+
} | {
|
|
11
|
+
progress: AssistantProgress;
|
|
12
|
+
};
|
|
13
|
+
/** @internal For senpi's session layer only. */
|
|
14
|
+
export type OrderedAgentRecord = InternalEvent & {
|
|
15
|
+
readonly sequence: number;
|
|
16
|
+
};
|
|
17
|
+
type Listener = (record: OrderedAgentRecord) => Promise<void> | void;
|
|
18
|
+
/** @internal Construction-time capability for senpi's session layer only. */
|
|
19
|
+
export interface InternalAgentChannel {
|
|
20
|
+
subscribe(listener: Listener): () => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @internal For senpi's session layer only. Pass at Agent construction; an existing Agent cannot be attached.
|
|
24
|
+
* Capture is held from the first listener until the last release, never by an idle channel.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createInternalAgentChannel(): InternalAgentChannel;
|
|
27
|
+
/** A run started without an internal listener gets no sink, and its loop reads the public stream exactly as before. */
|
|
28
|
+
export declare function internalLoopSink(channel: InternalAgentChannel | undefined): {
|
|
29
|
+
[internalProgressSink]?: ProgressSink;
|
|
30
|
+
};
|
|
31
|
+
/** Returns nothing, so it costs the public pipeline no microtask, while the owner has no internal listener. */
|
|
32
|
+
export declare function dispatchInternalAgent(channel: InternalAgentChannel | undefined, event: InternalEvent): Promise<void> | undefined;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=internal-events.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { subscribeAssistantCapture } from "@earendil-works/pi-ai";
|
|
2
|
+
export const internalProgressSink = Symbol("internalProgressSink");
|
|
3
|
+
const channels = new WeakMap();
|
|
4
|
+
/**
|
|
5
|
+
* @internal For senpi's session layer only. Pass at Agent construction; an existing Agent cannot be attached.
|
|
6
|
+
* Capture is held from the first listener until the last release, never by an idle channel.
|
|
7
|
+
*/
|
|
8
|
+
export function createInternalAgentChannel() {
|
|
9
|
+
const state = { sequence: 0, listeners: new Set() };
|
|
10
|
+
let releaseCapture;
|
|
11
|
+
const channel = Object.freeze({
|
|
12
|
+
subscribe(listener) {
|
|
13
|
+
state.listeners.add(listener);
|
|
14
|
+
releaseCapture ??= subscribeAssistantCapture();
|
|
15
|
+
return () => {
|
|
16
|
+
state.listeners.delete(listener);
|
|
17
|
+
if (state.listeners.size === 0) {
|
|
18
|
+
releaseCapture?.();
|
|
19
|
+
releaseCapture = undefined;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
channels.set(channel, state);
|
|
25
|
+
return channel;
|
|
26
|
+
}
|
|
27
|
+
/** A run started without an internal listener gets no sink, and its loop reads the public stream exactly as before. */
|
|
28
|
+
export function internalLoopSink(channel) {
|
|
29
|
+
if (!channel || !channels.get(channel)?.listeners.size)
|
|
30
|
+
return {};
|
|
31
|
+
return { [internalProgressSink]: (progress) => dispatchInternalAgent(channel, { progress }) };
|
|
32
|
+
}
|
|
33
|
+
/** Returns nothing, so it costs the public pipeline no microtask, while the owner has no internal listener. */
|
|
34
|
+
export function dispatchInternalAgent(channel, event) {
|
|
35
|
+
const state = channel && channels.get(channel);
|
|
36
|
+
if (!state?.listeners.size)
|
|
37
|
+
return undefined;
|
|
38
|
+
const record = Object.freeze({ ...event, sequence: ++state.sequence });
|
|
39
|
+
return (async () => {
|
|
40
|
+
for (const listener of state.listeners)
|
|
41
|
+
await listener(record);
|
|
42
|
+
})();
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=internal-events.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-agent-core",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.26-2",
|
|
4
4
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@earendil-works/chord": "0.99.1",
|
|
65
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.
|
|
66
|
-
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.
|
|
65
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.26-2",
|
|
66
|
+
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.26-2",
|
|
67
67
|
"diff": "9.0.0",
|
|
68
68
|
"ignore": "7.0.9",
|
|
69
69
|
"typebox": "1.3.34",
|