@aragon-agent/core 0.2.23 → 0.2.24

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 CHANGED
@@ -7,7 +7,14 @@ were written.
7
7
 
8
8
  ## Unreleased
9
9
 
10
- ### Added
10
+ ### Added
11
+
12
+ - **Steering 精确接收回执。** `Agent.steer(text, id?)` 兼容原单参数调用,
13
+ 历史接收后发出 `steering_accepted`,只确认当前批次的 ID;新增类型
14
+ `SteeringMessage` 和 `SteeringAcceptedEvent`,无新增运行时导出。
15
+ `drainSteeringItems()` 与原 `drainSteering()` 共用一个队列。
16
+ 接收早于异步压缩,未执行工具先补齐 skipped 结果;无工具响应在结束前再次检查
17
+ steering,避免流式输出期间提交的消息滞留。回执仅代表历史接收,不代表回答完成。
11
18
 
12
19
  - **Tail relief — the last rung of the compaction ladder.** `relieveTail` joins
13
20
  the root barrel with `TailReliefOptions` and `TailReliefResult` (85 → 86
@@ -0,0 +1,3 @@
1
+ import type { AgentLoopContext } from './agent-loop.js';
2
+ /** Accept one batch atomically with respect to async work and receipt listeners. */
3
+ export declare function acceptSteering(ctx: AgentLoopContext): void;
@@ -0,0 +1,15 @@
1
+ /** Accept one batch atomically with respect to async work and receipt listeners. */
2
+ export function acceptSteering(ctx) {
3
+ if (ctx.signal.aborted)
4
+ return;
5
+ const items = ctx.messageQueueManager.drainSteeringItems();
6
+ const ids = [];
7
+ for (const item of items) {
8
+ ctx.messageManager.push({ role: 'user', content: item.text, timestamp: Date.now() });
9
+ if (item.id !== undefined)
10
+ ids.push(item.id);
11
+ }
12
+ // The entire batch must be in history before listeners can abort or enqueue more work.
13
+ if (ids.length > 0)
14
+ ctx.emit({ type: 'steering_accepted', ids });
15
+ }
@@ -6,6 +6,7 @@
6
6
  * block detection and execution.
7
7
  */
8
8
  import { validateHistory } from './compaction.js';
9
+ import { acceptSteering } from './accept-steering.js';
9
10
  import { estimatePromptTokens } from '../llm/output-limits.js';
10
11
  // ---------------------------------------------------------------------------
11
12
  // Compaction checkpoint (context-auto-compaction §3.3)
@@ -239,6 +240,21 @@ function getAssistantText(message) {
239
240
  function getToolCalls(message) {
240
241
  return message.content.filter((b) => b.type === 'tool_call');
241
242
  }
243
+ /** Close the outstanding tool batch before steering creates the next user turn. */
244
+ function acceptToolSteering(ctx, remainingCalls) {
245
+ if (ctx.signal.aborted || !ctx.messageQueueManager.hasSteering())
246
+ return false;
247
+ for (const skipped of remainingCalls) {
248
+ ctx.messageManager.push({
249
+ role: 'tool_result',
250
+ toolCallId: skipped.toolCallId,
251
+ content: 'Tool execution skipped due to steering interrupt.',
252
+ isError: true,
253
+ });
254
+ }
255
+ acceptSteering(ctx);
256
+ return true;
257
+ }
242
258
  // ---------------------------------------------------------------------------
243
259
  // runAgentLoop
244
260
  // ---------------------------------------------------------------------------
@@ -263,14 +279,7 @@ export async function runAgentLoop(ctx) {
263
279
  let overflowRecovered = false;
264
280
  while (!ctx.signal.aborted) {
265
281
  // ----- Steering checkpoint (before LLM call) -----
266
- if (ctx.messageQueueManager.hasSteering()) {
267
- const steeringMessages = ctx.messageQueueManager.drainSteering();
268
- for (const text of steeringMessages) {
269
- const userMsg = { role: 'user', content: text, timestamp: Date.now() };
270
- ctx.messageManager.push(userMsg);
271
- }
272
- // Continue to LLM call with injected steering messages.
273
- }
282
+ acceptSteering(ctx);
274
283
  turnIndex += 1;
275
284
  // ----- Context pressure checkpoint (context-auto-compaction §3.3) -----
276
285
  //
@@ -375,6 +384,8 @@ export async function runAgentLoop(ctx) {
375
384
  lastUsage = usage;
376
385
  overflowRecovered = false;
377
386
  ctx.messageManager.push(assistantMessage);
387
+ if (ctx.signal.aborted)
388
+ break;
378
389
  // ----- Process tool calls -----
379
390
  const toolCalls = getToolCalls(assistantMessage);
380
391
  const hasToolCalls = toolCalls.length > 0;
@@ -382,12 +393,7 @@ export async function runAgentLoop(ctx) {
382
393
  if (hasToolCalls) {
383
394
  // Steering checkpoint: if steering arrived during the LLM call,
384
395
  // skip tool execution and inject steering messages instead.
385
- if (ctx.messageQueueManager.hasSteering()) {
386
- const steeringMessages = ctx.messageQueueManager.drainSteering();
387
- for (const text of steeringMessages) {
388
- const userMsg = { role: 'user', content: text, timestamp: Date.now() };
389
- ctx.messageManager.push(userMsg);
390
- }
396
+ if (acceptToolSteering(ctx, toolCalls)) {
391
397
  // Loop continues — LLM will see the steering messages.
392
398
  continue;
393
399
  }
@@ -396,23 +402,7 @@ export async function runAgentLoop(ctx) {
396
402
  if (ctx.signal.aborted)
397
403
  break;
398
404
  // Check steering between individual tool executions.
399
- if (ctx.messageQueueManager.hasSteering()) {
400
- // Inject remaining tool results as errors so LLM knows they were skipped.
401
- const remainingCalls = toolCalls.slice(toolCalls.indexOf(toolCall));
402
- for (const skipped of remainingCalls) {
403
- ctx.messageManager.push({
404
- role: 'tool_result',
405
- toolCallId: skipped.toolCallId,
406
- content: 'Tool execution skipped due to steering interrupt.',
407
- isError: true,
408
- });
409
- }
410
- // Inject steering messages.
411
- const steeringMessages = ctx.messageQueueManager.drainSteering();
412
- for (const text of steeringMessages) {
413
- const userMsg = { role: 'user', content: text, timestamp: Date.now() };
414
- ctx.messageManager.push(userMsg);
415
- }
405
+ if (acceptToolSteering(ctx, toolCalls.slice(toolCalls.indexOf(toolCall)))) {
416
406
  steeringInterrupted = true;
417
407
  break;
418
408
  }
@@ -508,7 +498,9 @@ export async function runAgentLoop(ctx) {
508
498
  }
509
499
  // ----- Exit or follow-up checkpoint -----
510
500
  if (!hasToolCalls && !hasCodeBlocks) {
511
- // No tools and no code blocks — check for follow-up messages.
501
+ // Give streaming and turn_end steering priority over follow-up and normal exit.
502
+ if (ctx.messageQueueManager.hasSteering())
503
+ continue;
512
504
  if (ctx.messageQueueManager.hasFollowUp()) {
513
505
  const followUpMessages = ctx.messageQueueManager.drainFollowUp();
514
506
  for (const text of followUpMessages) {
@@ -128,8 +128,8 @@ export declare class Agent {
128
128
  * @returns An unsubscribe function.
129
129
  */
130
130
  subscribe(listener: AgentEventListener): () => void;
131
- /** Inject a high-priority steering message (interrupts tool execution). */
132
- steer(message: string): void;
131
+ /** Queue high-priority text; an optional ID is echoed only after history acceptance. */
132
+ steer(message: string, id?: string): void;
133
133
  /** Queue a low-priority follow-up message (consumed after end_turn). */
134
134
  followUp(message: string): void;
135
135
  setModel(model: ModelRef): void;
@@ -156,9 +156,9 @@ export class Agent {
156
156
  // =========================================================================
157
157
  // Message queues
158
158
  // =========================================================================
159
- /** Inject a high-priority steering message (interrupts tool execution). */
160
- steer(message) {
161
- this.messageQueueManager.pushSteering(message);
159
+ /** Queue high-priority text; an optional ID is echoed only after history acceptance. */
160
+ steer(message, id) {
161
+ this.messageQueueManager.pushSteering(message, id);
162
162
  }
163
163
  /** Queue a low-priority follow-up message (consumed after end_turn). */
164
164
  followUp(message) {
@@ -7,13 +7,20 @@
7
7
  * Follow-up messages have low priority: they are consumed only when the
8
8
  * Agent loop would otherwise exit (end_turn with no pending work).
9
9
  */
10
+ /** Queued text with an optional host-supplied opaque receipt identifier. */
11
+ export interface SteeringMessage {
12
+ readonly text: string;
13
+ readonly id?: string;
14
+ }
10
15
  export declare class MessageQueueManager {
11
16
  private readonly steeringQueue;
12
17
  private readonly followUpQueue;
13
18
  /** Enqueue a high-priority steering message. */
14
- pushSteering(message: string): void;
19
+ pushSteering(message: string, id?: string): void;
15
20
  /** Drain and return all pending steering messages (empties the queue). */
16
21
  drainSteering(): string[];
22
+ /** Drain all pending envelopes in FIFO order (empties the same queue). */
23
+ drainSteeringItems(): SteeringMessage[];
17
24
  /** Whether there are pending steering messages. */
18
25
  hasSteering(): boolean;
19
26
  /** Enqueue a low-priority follow-up message. */
@@ -14,11 +14,15 @@ export class MessageQueueManager {
14
14
  // Steering
15
15
  // -----------------------------------------------------------------------
16
16
  /** Enqueue a high-priority steering message. */
17
- pushSteering(message) {
18
- this.steeringQueue.push(message);
17
+ pushSteering(message, id) {
18
+ this.steeringQueue.push({ text: message, ...(id !== undefined ? { id } : {}) });
19
19
  }
20
20
  /** Drain and return all pending steering messages (empties the queue). */
21
21
  drainSteering() {
22
+ return this.drainSteeringItems().map((item) => item.text);
23
+ }
24
+ /** Drain all pending envelopes in FIFO order (empties the same queue). */
25
+ drainSteeringItems() {
22
26
  return this.steeringQueue.splice(0);
23
27
  }
24
28
  /** Whether there are pending steering messages. */
package/dist/index.d.ts CHANGED
@@ -41,13 +41,14 @@ export { createSkillFindTool } from './skills/skill-find-tool.js';
41
41
  export { computeToolPolicy, evaluateToolCall } from './skills/tool-policy.js';
42
42
  export { SKILL_CATALOG_MAX_BYTES, SKILL_BODY_MAX_BYTES, SKILL_RESULT_MAX_BYTES, ALWAYS_SKILLS_MAX_BYTES, SKILL_MD_MAX_BYTES, SKILL_DESC_LINE_MAX, SKILL_FILES_MAX, SKILL_FIND_MAX_BYTES, SKILL_NAME_PATTERN, } from './skills/constants.js';
43
43
  export type { SkillScope, SkillActivation, SkillFrontmatter, SkillRecord, SkillFileRef, SkillHost, SkillManifest, SkillValidationIssue, SkillCatalogOptions, SkillBodyOptions, SkillFindOptions, SkillIntegrity, SkillUsageStat, SkillUsageMap, SkillInvocationOptions, SkillPlatform, SkillPolicyView, SkillToolPolicyMode, ToolPolicyDecision, ToolPolicyInput, ToolPolicySource, ToolPolicyVerdict, } from './skills/types.js';
44
- export type { AgentEvent, AgentEventListener, AgentStartEvent, AgentEndEvent, TurnStartEvent, TurnEndEvent, MessageUpdateEvent, ToolExecutionStartEvent, ToolExecutionEndEvent, CodeExecutionStartEvent, CodeExecutionEndEvent, CompactionStartEvent, CompactionEndEvent, } from './types.js';
44
+ export type { AgentEvent, AgentEventListener, AgentStartEvent, AgentEndEvent, TurnStartEvent, SteeringAcceptedEvent, TurnEndEvent, MessageUpdateEvent, ToolExecutionStartEvent, ToolExecutionEndEvent, CodeExecutionStartEvent, CodeExecutionEndEvent, CompactionStartEvent, CompactionEndEvent, } from './types.js';
45
45
  export { Agent } from './engine/agent.js';
46
46
  export type { AgentConfig, AgentState } from './engine/agent.js';
47
47
  export type { ModelRef } from './engine/agent-loop.js';
48
48
  export type { CodeActSandbox, CodeActResult } from './engine/agent-loop.js';
49
49
  export { MessageManager } from './engine/message-manager.js';
50
50
  export { MessageQueueManager } from './engine/steering.js';
51
+ export type { SteeringMessage } from './engine/steering.js';
51
52
  export { IdleWatchdog } from './engine/watchdog.js';
52
53
  export { findSafeCutIndices, planCompaction, relieveTail, validateHistory, } from './engine/compaction.js';
53
54
  export type { CompactionPlan, HistoryCheck, PlanCompactionOptions, TailReliefOptions, TailReliefResult, ValidateHistoryOptions, } from './engine/compaction.js';
package/dist/types.d.ts CHANGED
@@ -17,6 +17,11 @@ export interface AgentEndEvent {
17
17
  export interface TurnStartEvent {
18
18
  type: 'turn_start';
19
19
  }
20
+ /** Text for these opaque IDs has entered history, not necessarily reached the model. */
21
+ export interface SteeringAcceptedEvent {
22
+ type: 'steering_accepted';
23
+ ids: readonly string[];
24
+ }
20
25
  export interface TurnEndEvent {
21
26
  type: 'turn_end';
22
27
  message: AssistantMessage;
@@ -107,6 +112,6 @@ export interface CompactionEndEvent {
107
112
  durationMs: number;
108
113
  }
109
114
  /** Union of all events emitted by the Agent engine. */
110
- export type AgentEvent = AgentStartEvent | AgentEndEvent | TurnStartEvent | TurnEndEvent | MessageUpdateEvent | ToolExecutionStartEvent | ToolExecutionEndEvent | CodeExecutionStartEvent | CodeExecutionEndEvent | CompactionStartEvent | CompactionEndEvent;
115
+ export type AgentEvent = AgentStartEvent | AgentEndEvent | TurnStartEvent | SteeringAcceptedEvent | TurnEndEvent | MessageUpdateEvent | ToolExecutionStartEvent | ToolExecutionEndEvent | CodeExecutionStartEvent | CodeExecutionEndEvent | CompactionStartEvent | CompactionEndEvent;
111
116
  /** Callback signature for Agent event listeners. */
112
117
  export type AgentEventListener = (event: AgentEvent) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/core",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "AragonAgent core engine — LLM provider adapters, streaming, tool registry/execution, steering, idle watchdog, and an optional isolated-vm sandbox.",
5
5
  "license": "MIT",
6
6
  "author": "AragonAgent contributors",