Skip to content

feat(agent): sync AgentController capability from tegg#5968

Merged
jerryliang64 merged 1 commit into
nextfrom
sync/agent-controller-from-tegg
Jun 11, 2026
Merged

feat(agent): sync AgentController capability from tegg#5968
jerryliang64 merged 1 commit into
nextfrom
sync/agent-controller-from-tegg

Conversation

@jerryliang64

@jerryliang64 jerryliang64 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Motivation

egg's @AgentController capability was the initial port (#5827, March 2026). Since then all active development happened on the standalone tegg repo, where the agent message model and runtime evolved significantly. This PR brings egg's next branch up to tegg's current implementation as the source of truth, replacing egg's older implementation.

The agent surface in egg is self-contained — the changed types (MessageObject/AgentStreamMessage → SDK-aligned AgentMessage) are consumed only by the agent-runtime + controller agent files, so this has no impact outside the agent capability (verified: no external consumers of the removed symbols).

Scope

  • types(agent-runtime) — switch to the SDK-aligned AgentMessage model (replaces the OpenAI-style MessageObject surface); add latestRunId / getLatestRunId, thread metadata, GetThreadOptions, AgentTimeoutError.
  • agent-runtime — port AgentRuntime / OSSAgentStore / MessageConverter / RunBuilder / SSEWriter to current behavior: per-thread creation-time index, persist-on-abort, session-committed cancel gating, run/thread metadata, getRunStream / getLatestRunId.
  • controller-decorator — add getLatestRunId (GET /threads/:id/latest-run) and getRunStream (GET /runs/:id/stream) routes, multi-param routing, and the isSessionCommitted hook on AgentHandler.
  • controller-plugin — wire the getLatestRunId delegate and getRunStream SSE delegate in AgentControllerObject.
  • tegg — re-export the SDK-aligned agent message types from the barrel.
  • tests — port the agent-runtime + controller-decorator agent tests; update the @eggjs/tegg-types export snapshot.

Convention adaptation applied during the port: ESM .ts import suffixes, named util imports, mocha→vitest test hooks. No package renames were needed (the agent surface only uses same-named packages).

Test evidence

Run locally with Node 22.22 (repo requires >= 22.18):

  • oxfmt --check — all 26 changed files pass
  • oxlint --type-aware --type-check --quiet — exit 0 (no errors)
  • tsgo --noEmit (typecheck) — clean for @eggjs/tegg-types, @eggjs/agent-runtime, @eggjs/controller-decorator, @eggjs/controller-plugin, @eggjs/tegg
  • vitest run over the 5 affected packages — 42 files / 323 tests passed (with CI flags --testTimeout 20000)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Persistent, reconnectable run streaming with ability to resume from any point
    • Latest run ID retrieval per thread with automatic tracking
    • Thread metadata support on creation and updates
    • Enhanced run cancellation with configurable timeout handling
  • Improvements

    • SSE streaming now includes keepalive signals for better connection stability
    • Automatic partial transcript preservation on abnormal run termination
    • Thread message filtering and query options for better control

Replace egg's earlier AgentController implementation with tegg's current
implementation as the source of truth (egg's was the initial March port;
tegg has since evolved the message model and runtime).

- types(agent-runtime): switch to the SDK-aligned AgentMessage model
  (replaces the OpenAI-style MessageObject surface), add latestRunId /
  getLatestRunId, thread metadata, GetThreadOptions and AgentTimeoutError
- agent-runtime: port AgentRuntime/OSSAgentStore/MessageConverter/RunBuilder/
  SSEWriter to the current behavior (per-thread creation-time index,
  persist-on-abort, session-committed cancel gating, run/thread metadata,
  getRunStream / getLatestRunId)
- controller-decorator: add getLatestRunId (GET /threads/:id/latest-run) and
  getRunStream (GET /runs/:id/stream) routes, multi-param routing, and the
  isSessionCommitted hook on AgentHandler
- controller-plugin: wire the getLatestRunId delegate and getRunStream SSE
  delegate in AgentControllerObject
- tegg: re-export SDK-aligned agent message types from the barrel
- tests: port agent-runtime + controller-decorator agent tests and update the
  @eggjs/tegg-types export snapshot

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR substantially refactors the agent runtime to add executor commit awareness, persistent JSONL-backed SSE streaming with reconnection support, thread metadata merging, and enhanced cancellation semantics. All changes maintain backward compatibility at the public method level while replacing the underlying execution and streaming infrastructure.

Changes

Agent Runtime Commit-Aware Refactor with Persistent SSE Streaming

Layer / File(s) Summary
Type contract updates for new message model
tegg/core/types/src/agent-runtime/AgentMessage.ts, AgentRuntime.ts, AgentStore.ts, errors.ts, tegg/core/tegg/src/agent.ts, tegg/core/controller-decorator/src/decorator/agent/AgentHandler.ts, tegg/core/controller-decorator/test/fixtures/AgentFooController.ts
SDK-aligned AgentMessage union replaces AgentStreamMessage, new InputContentPart discriminated types replace content blocks, and new option/stream/event wrapper types added; old MessageObject and stream-message types removed.
Store persistence and indexing foundation
tegg/core/agent-runtime/src/AgentStoreUtils.ts, OSSAgentStore.ts
Adds reverseMs/dateBucket time utilities for bucketing, per-thread metadata locking for concurrent-safe updates, latestRunId pointer tracking, and background activity index sidecar writes with best-effort failure handling.
Message conversion for new type model
tegg/core/agent-runtime/src/MessageConverter.ts
Replaces stream-message handling with extractUsage (result messages only), filterForStorage (removes stream_events), and toAgentMessages (input→agent with system-role filtering).
SSE writer comment and HTTP support
tegg/core/agent-runtime/src/SSEWriter.ts, HttpSSEWriter.ts
Adds writeComment method to both interfaces for emitting keepalive/heartbeat SSE frames.
RunBuilder refactor for new completion model
tegg/core/agent-runtime/src/RunBuilder.ts
Removes output parameter from complete, accepts only optional usage; expands failure state to allow transitioning from Cancelling during cancel watchdog timeouts.
Core runtime execution with commit awareness
tegg/core/agent-runtime/src/AgentRuntime.ts (execution model: lines 1–117, 126–178, 199–279, 298–372)
Per-run task state tracks committed flag and abort controller; syncRun/asyncRun persist partial transcripts on non-success and skip TOCTOU overwrites of terminal statuses; executor hook isSessionCommitted determines commit; partial messages appended at most once via persistPartialMessages helper.
Persistent JSONL-backed SSE streaming with reconnection
tegg/core/agent-runtime/src/AgentRuntime.ts (streaming: lines 381–556, 702–768)
streamRun decouples background execution from SSE connection, writes JSONL event log; getRunStream replays from lastSeq and streams live events; two-phase delivery via JSONL replay + EventEmitter; heartbeat comments for keepalive.
Commit-aware cancellation with timeout handling
tegg/core/agent-runtime/src/AgentRuntime.ts (cancellation: lines 557–625, 786–860)
cancelRun waits for executor commit up to cancelCommitTimeoutMs, fails with AgentTimeoutError on timeout (leaving thread untouched if not committed), aborts task and transitions to cancelled on commit; destroy cleans up buffered listeners.
Controller route parameters and SSE delegation
tegg/core/controller-decorator/src/decorator/agent/AgentController.ts, AgentControllerObject.ts
Routes now use structured params array for multi-parameter endpoints (e.g., getRunStream with path + query); createNotImplemented accepts paramCount to generate correct stub signatures; getRunStream injected as delegate parsing lastSeq and creating HttpSSEWriter.
Controller test expectations for new routes
tegg/core/controller-decorator/test/AgentController.test.ts
Verifies getLatestRunId (GET /threads/:id/latest-run) and getRunStream (GET /runs/:id/stream with lastSeq query param) route definitions, HTTP method metadata, and parameter registration.
Test infrastructure for storage simulation
tegg/core/agent-runtime/test/helpers.ts, OSSObjectStorageClient.test.ts
mockFn utility records calls and queues return values; MapStorageClient variants gain failPutWhenKeyMatches/delayPutWhenKeyMatches for deterministic failure/timing simulation; replaces vitest vi.fn mocks.
Core runtime test suite alignment with new execution model
tegg/core/agent-runtime/test/AgentRuntime.test.ts
Extensive rewrite: executors emit AgentMessage with string type/role and usage: {input_tokens, output_tokens}; validates event sequencing, reconnection replay with lastSeq, commit-waiting semantics, abort message persistence, and TOCTOU resistance; adds getLatestRunId and expanded cancelRun coverage.
Metadata behavior test suite
tegg/core/agent-runtime/test/AgentRuntime.metadata.test.ts
Validates metadata persistence on runs, thread initialization/shallow-merge, invalid metadata rejection (HTTP 400), and metadata preservation across syncRun/asyncRun/streamRun.
OSSAgentStore comprehensive test expansion
tegg/core/agent-runtime/test/OSSAgentStore.test.ts
Extensively expanded: latestRunId pointer semantics, activity index bucketing/ordering by reverse timestamp, custom logger and console.warn fallback on index write failures, drain/cleanup of pending writes, prefix normalization, and confirmation that run operations never touch activity index.
MessageConverter test migration to new types
tegg/core/agent-runtime/test/MessageConverter.test.ts
Rewrites test suite: extractUsage validates token accumulation from result messages, filterForStorage removes stream_events, toAgentMessages converts inputs while filtering system roles.
AgentStoreUtils test coverage for time utilities
tegg/core/agent-runtime/test/AgentStoreUtils.test.ts
New suite validates TS_MAX_MS 13-digit ceiling, reverseMs zero-padded output and lexicographic monotonicity, and dateBucket UTC bucketing with boundary validation.
HttpSSEWriter test expansion for comment support
tegg/core/agent-runtime/test/HttpSSEWriter.test.ts
Adds tests for writeComment SSE frame formatting, behavior after connection close, and header sending on first comment write.
RunBuilder test migration for completion model
tegg/core/agent-runtime/test/RunBuilder.test.ts
Removes output expectations from snapshots; calls complete(usage) instead of complete(output, usage); tests failure transition from Cancelling state.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • eggjs/egg#5823: Updates the same agent-runtime storage layer with different timestamp helpers and extends OSSAgentStore with metadata indexing and getLatestRunId.
  • eggjs/egg#5824: Directly related refactor of AgentRuntime sync/async/stream execution, SSE streaming, and public hooks.
  • eggjs/egg#5827: Related controller-plugin integration changes that route getRunStream/SSE calls through AgentRuntime.

Suggested Reviewers

  • akitaSummer
  • gxkl
  • fengmk2

🐰 A rabbit hops through time with streaming so fine,
Commits are tracked, events aligned,
Reconnect, replay, no thread left behind—
Persistence and peace, a design most divine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(agent): sync AgentController capability from tegg' clearly and specifically describes the main change: synchronizing AgentController implementation from the standalone tegg repository.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/agent-controller-from-tegg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@killagu killagu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.50249% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.50%. Comparing base (1836bce) to head (13c1446).

Files with missing lines Patch % Lines
tegg/core/agent-runtime/src/OSSAgentStore.ts 97.50% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             next    #5968      +/-   ##
==========================================
+ Coverage   85.32%   85.50%   +0.18%     
==========================================
  Files         670      669       -1     
  Lines       19553    19825     +272     
  Branches     3864     3917      +53     
==========================================
+ Hits        16683    16952     +269     
- Misses       2479     2481       +2     
- Partials      391      392       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying egg with  Cloudflare Pages  Cloudflare Pages

Latest commit: 13c1446
Status: ✅  Deploy successful!
Preview URL: https://26cba2ad.egg-cci.pages.dev
Branch Preview URL: https://sync-agent-controller-from-t.egg-cci.pages.dev

View logs

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant enhancements to the AgentRuntime and OSSAgentStore to support session persistence, robust run cancellation, and improved event streaming. Key changes include adding an isSessionCommitted hook for executors, implementing a background event buffer for streaming runs, and introducing an activity-time index for threads. The review feedback correctly identifies several critical issues: the use of synchronous file I/O in hot paths, potential memory leaks in heartbeat timers, and the need for robust error normalization when handling unknown caught exceptions. Additionally, the reviewer highlights the risk of unbounded disk usage from event logs and suggests improvements for the exclusive write lock mechanism in the store.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +457 to +466
private pushEvent(buffer: RunEventBuffer, type: string, data: unknown): void {
const event: StreamEvent = {
seq: ++buffer.lastSeq,
type,
data,
ts: Date.now(),
};
appendFileSync(buffer.filePath, JSON.stringify(event) + '\n');
buffer.emitter.emit('event', event);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using synchronous file system operations like appendFileSync inside the hot path of a streaming API (which can be called dozens of times per second per active stream for token deltas) blocks the single-threaded Node.js event loop. This can severely degrade the performance and throughput of the entire application under concurrent load.

Consider using a non-blocking fs.WriteStream to write events. You can create the write stream when initializing the RunEventBuffer and close it when the stream finishes.

  private pushEvent(buffer: RunEventBuffer, type: string, data: unknown): void {
    const event: StreamEvent = {
      seq: ++buffer.lastSeq,
      type,
      data,
      ts: Date.now(),
    };
    if (buffer.writeStream) {
      buffer.writeStream.write(JSON.stringify(event) + '\n');
    } else {
      appendFileSync(buffer.filePath, JSON.stringify(event) + '\n');
    }
    buffer.emitter.emit('event', event);
  }

Comment on lines +737 to +741
const waitForEvent = () =>
new Promise<'event' | 'heartbeat'>((resolve) => {
waitResolve = () => resolve('event');
setTimeout(() => resolve('heartbeat'), HEARTBEAT_INTERVAL_MS);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In waitForEvent, the setTimeout timer is not cleared when the promise resolves via waitResolve (which happens when a real-time event is received). This causes a resource leak where active timers and their closures accumulate in the event loop until they eventually fire after 10 seconds. Under high event frequency, this can lead to significant memory overhead and CPU churn.

Clear the timeout when waitResolve is called to prevent this leak.

      const waitForEvent = () =>
        new Promise<'event' | 'heartbeat'>((resolve) => {
          const timer = setTimeout(() => resolve('heartbeat'), HEARTBEAT_INTERVAL_MS);
          waitResolve = () => {
            clearTimeout(timer);
            resolve('event');
          };
        });

Comment on lines 536 to +544
try {
await this.store.updateRun(run.id, rb.fail(err as Error));
const currentRun = await this.store.getRun(runId);
if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {
await this.store.updateRun(runId, rb.fail(err as Error));
}
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);
}

// event: thread.run.failed
if (!writer.closed) {
writer.writeEvent(AgentSSEEvent.ThreadRunFailed, rb.snapshot());
}
this.pushEvent(buffer, 'error', { message: (err as Error).message, runId });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When handling caught exceptions of type unknown, casting err as Error and directly accessing (err as Error).message can lead to runtime errors or undefined values if the thrown value is not an instance of Error (e.g., a string or a plain object).

Following the repository's general guidelines, we should check if the caught value is an Error instance before accessing its properties, and use String(err) as a fallback to ensure a meaningful error message.

Let's normalize err to an Error instance first.

Suggested change
try {
await this.store.updateRun(run.id, rb.fail(err as Error));
const currentRun = await this.store.getRun(runId);
if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {
await this.store.updateRun(runId, rb.fail(err as Error));
}
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);
}
// event: thread.run.failed
if (!writer.closed) {
writer.writeEvent(AgentSSEEvent.ThreadRunFailed, rb.snapshot());
}
this.pushEvent(buffer, 'error', { message: (err as Error).message, runId });
const errorInstance = err instanceof Error ? err : new Error(String(err));
try {
const currentRun = await this.store.getRun(runId);
if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {
await this.store.updateRun(runId, rb.fail(errorInstance));
}
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);
}
this.pushEvent(buffer, 'error', { message: errorInstance.message, runId });
References
  1. When creating a new Error from a caught exception, check if the caught value is an Error instance before accessing its message property. Use String(err) as a fallback for non-Error values to ensure a meaningful error message.

Comment on lines 272 to 277
try {
await this.store.updateRun(run.id, rb.fail(err as Error));
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after syncRun error:', storeErr);
}
throw err;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure safe error handling when err is of type unknown, normalize it to an Error instance before passing it to rb.fail. This prevents potential runtime issues if a non-Error value is thrown.

Suggested change
try {
await this.store.updateRun(run.id, rb.fail(err as Error));
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after syncRun error:', storeErr);
}
throw err;
try {
const errorInstance = err instanceof Error ? err : new Error(String(err));
await this.store.updateRun(run.id, rb.fail(errorInstance));
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after syncRun error:', storeErr);
}
throw err;
References
  1. When creating a new Error from a caught exception, check if the caught value is an Error instance before accessing its message property. Use String(err) as a fallback for non-Error values to ensure a meaningful error message.

Comment on lines 358 to 365
try {
const currentRun = await this.store.getRun(run.id);
if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {
await this.store.updateRun(run.id, rb.fail(err as Error));
}
} catch (storeErr) {
// TODO: need a background expiry mechanism to clean up runs stuck in non-terminal states
// (e.g. in_progress or cancelling) when store writes fail persistently.
this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Normalize err to an Error instance before passing it to rb.fail to ensure robust error handling for non-Error thrown values.

Suggested change
try {
const currentRun = await this.store.getRun(run.id);
if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {
await this.store.updateRun(run.id, rb.fail(err as Error));
}
} catch (storeErr) {
// TODO: need a background expiry mechanism to clean up runs stuck in non-terminal states
// (e.g. in_progress or cancelling) when store writes fail persistently.
this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);
}
try {
const currentRun = await this.store.getRun(run.id);
if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) {
const errorInstance = err instanceof Error ? err : new Error(String(err));
await this.store.updateRun(run.id, rb.fail(errorInstance));
}
} catch (storeErr) {
this.logger.error('[AgentRuntime] failed to update run status after error:', storeErr);
}
References
  1. When creating a new Error from a caught exception, check if the caught value is an Error instance before accessing its message property. Use String(err) as a fallback for non-Error values to ensure a meaningful error message.

Comment on lines +197 to +203
private async runExclusiveThreadMetaWrite<T>(threadId: string, fn: () => Promise<T>): Promise<T> {
const previous = this.threadMetaWriteTails.get(threadId) ?? Promise.resolve();
let release!: () => void;
const current = new Promise<void>((resolve) => {
release = resolve;
});
const tail = previous.then(() => current);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In runExclusiveThreadMetaWrite, the promise chain is constructed using previous.then(() => current). If any promise in the chain rejects (e.g., due to an unhandled rejection or an unexpected error in a previous operation), the rejection will propagate down the chain, causing all subsequent write operations for that thread to reject and fail permanently.

To make the lock chain robust against unexpected rejections, catch any errors from the previous promise before chaining the next one.

Suggested change
private async runExclusiveThreadMetaWrite<T>(threadId: string, fn: () => Promise<T>): Promise<T> {
const previous = this.threadMetaWriteTails.get(threadId) ?? Promise.resolve();
let release!: () => void;
const current = new Promise<void>((resolve) => {
release = resolve;
});
const tail = previous.then(() => current);
private async runExclusiveThreadMetaWrite<T>(threadId: string, fn: () => Promise<T>): Promise<T> {
const previous = this.threadMetaWriteTails.get(threadId) ?? Promise.resolve();
let release!: () => void;
const current = new Promise<void>((resolve) => {
release = resolve;
});
const tail = previous.catch(() => {}).then(() => current);

Comment on lines +33 to +34
const EVENT_DIR = join(tmpdir(), 'agent-runtime-events');
const DEFAULT_CANCEL_COMMIT_TIMEOUT_MS = 30_000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The EVENT_DIR directory is created in the system's temporary directory (tmpdir()), and a new .jsonl file is created for every streaming run. Since these files are never deleted or cleaned up, they will accumulate indefinitely over the lifetime of the server, potentially leading to disk space exhaustion.

Consider implementing a background cleanup job or a retention policy (e.g., deleting files older than 24 hours) to prevent unbounded disk space usage.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying egg-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 13c1446
Status: ✅  Deploy successful!
Preview URL: https://07a08c49.egg-v3.pages.dev
Branch Preview URL: https://sync-agent-controller-from-t.egg-v3.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tegg/core/agent-runtime/src/AgentRuntime.ts (1)

131-138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate thread metadata on the public createThread() path too.

ensureThread() rejects non-object metadata, but createThread() forwards options?.metadata straight to the store. That lets null/arrays reach persisted thread metadata through the thread-creation API even though the runtime now treats thread metadata as object-shaped.

Suggested fix
 async createThread(options?: CreateThreadOptions): Promise<ThreadObject> {
-  const thread = await this.store.createThread(options?.metadata);
+  const thread = await this.store.createThread(validateMetadata(options?.metadata));
   return {
     id: thread.id,
     object: AgentObjectType.Thread,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/agent-runtime/src/AgentRuntime.ts` around lines 131 - 138,
createThread forwards options?.metadata straight to this.store.createThread
allowing null/arrays to persist; call the existing ensureThread validator to
validate/normalize metadata before persisting. In AgentRuntime.createThread, run
const metadata = ensureThread(options?.metadata) (or similar) and pass that
validated metadata to this.store.createThread, and use that same metadata in the
returned ThreadObject so persisted and returned thread metadata are
object-shaped and consistent with ensureThread's rules.
🧹 Nitpick comments (4)
tegg/core/controller-decorator/src/decorator/agent/AgentController.ts (2)

17-21: ⚡ Quick win

Consider using a discriminated union for type-safe parameter metadata.

The current interface allows name to be optional for all param types, but pathParam and query always require name while body never uses it. This forces non-null assertions at lines 156 and 159. A discriminated union would make this contract compile-time safe:

type AgentRouteParam = 
  | { index: number; type: 'body' }
  | { index: number; type: 'pathParam'; name: string }
  | { index: number; type: 'query'; name: string };

This eliminates the ! assertions and prevents accidental misuse (e.g., forgetting to add name for a path parameter).

♻️ Refactored type definition
-interface AgentRouteParam {
-  index: number;
-  type: 'body' | 'pathParam' | 'query';
-  name?: string;
-}
+type AgentRouteParam = 
+  | { index: number; type: 'body' }
+  | { index: number; type: 'pathParam'; name: string }
+  | { index: number; type: 'query'; name: string };

Then at lines 156 and 159, remove the !:

-          HTTPInfoUtil.setHTTPMethodParamName(param.name!, param.index, constructor, route.methodName);
+          HTTPInfoUtil.setHTTPMethodParamName(param.name, param.index, constructor, route.methodName);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/controller-decorator/src/decorator/agent/AgentController.ts` around
lines 17 - 21, Change the loose AgentRouteParam interface into a discriminated
union so parameter metadata is type-safe: replace the current interface
AgentRouteParam with a union of { index: number; type: 'body' } | { index:
number; type: 'pathParam'; name: string } | { index: number; type: 'query';
name: string }; then update all code that accesses AgentRouteParam (notably the
logic in AgentController that currently uses non-null assertions on param.name)
to rely on the discriminant (param.type) and remove the `!` assertions, e.g.,
when param.type === 'pathParam' or 'query' safely access param.name and when
'body' avoid using name. Ensure any helper functions or type guards reflect the
new union.

34-53: ⚡ Quick win

Stub generator only supports up to 2 parameters.

The paramCount >= 2 branch creates a function with exactly 2 parameters, so routes with 3+ parameters would get a stub whose function.length doesn't match paramCount, breaking the framework's param validation contract (line 31 comment).

Currently no routes exceed 2 parameters, but this limits future extensibility. Consider either:

  • Adding a constraint (e.g., if (paramCount > 2) throw new Error(...)) to fail fast
  • Using rest parameters: fn = async function (..._args: unknown[]) { ... } (though this makes function.length always 0)
  • Generating the function dynamically to match the exact parameter count
♻️ Option 1: Add constraint to fail fast
 function createNotImplemented(methodName: string, paramCount: number) {
+  if (paramCount > 2) {
+    throw new Error(`createNotImplemented does not support routes with more than 2 parameters (${methodName} has ${paramCount})`);
+  }
   let fn;
   if (paramCount >= 2) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/controller-decorator/src/decorator/agent/AgentController.ts` around
lines 34 - 53, createNotImplemented currently maps any paramCount >= 2 to a
2-arg stub, which breaks the framework's param validation for routes with >2
params; update createNotImplemented to fail fast by throwing an Error when
paramCount > 2 (e.g. at the start of createNotImplemented check if paramCount >
2 and throw a clear error mentioning methodName and paramCount), then keep
existing branches for 0,1,2 parameters and continue to call
AgentInfoUtil.setNotImplemented(fn).
tegg/plugin/controller/src/lib/AgentControllerObject.ts (1)

267-267: 💤 Low value

Unnecessary type assertion in parseInt call.

The as string assertion is redundant since lastSeq is already typed as string | undefined in the function signature. parseInt(undefined, 10) returns NaN, and NaN || 0 correctly returns 0, so the code works but the assertion reduces clarity.

♻️ Cleaner alternatives

Option 1: Use nullish coalescing:

-      const seq = parseInt(lastSeq as string, 10) || 0;
+      const seq = parseInt(lastSeq ?? '', 10) || 0;

Option 2: Explicit check:

-      const seq = parseInt(lastSeq as string, 10) || 0;
+      const seq = lastSeq ? parseInt(lastSeq, 10) || 0 : 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/plugin/controller/src/lib/AgentControllerObject.ts` at line 267, The
parseInt call in AgentControllerObject (where const seq = parseInt(lastSeq as
string, 10) || 0;) uses an unnecessary type assertion; remove the "as string"
and either call parseInt(lastSeq ?? undefined, 10) with the existing || 0
fallback or explicitly handle undefined (e.g., const seq = parseInt(lastSeq ??
'', 10) || 0) so the code is clearer—update the line in AgentControllerObject
accordingly without changing surrounding logic.
tegg/core/agent-runtime/test/OSSObjectStorageClient.test.ts (1)

8-73: ⚡ Quick win

Remove dead code in the first section of mockFn().

Lines 9–39 define fn and attach mock methods to it, but this fn is never returned. Instead, lines 41–72 redefine wrappedFn with the same logic, and line 72 returns wrappedFn. The first section (lines 9–39) is unreachable dead code.

♻️ Proposed cleanup
 /** Simple mock function helper for mocha tests. */
 function mockFn() {
   const calls: any[][] = [];
   let nextResults: Array<{ type: 'resolve' | 'reject'; value: any }> = [];
-  const fn = (...args: any[]) => {
-    calls.push(args);
-    const result = nextResults.shift();
-    if (result) {
-      return result.type === 'resolve' ? Promise.resolve(result.value) : Promise.reject(result.value);
-    }
-    return Promise.resolve({});
-  };
-  fn.mock = { calls };
-  fn.mockResolvedValue = (val: any) => {
-    nextResults = [];
-    fn.mockResolvedValueOnce(val);
-    nextResults = nextResults.map(() => ({ type: 'resolve' as const, value: val }));
-    (fn as any)._defaultResult = { type: 'resolve', value: val };
-    return fn;
-  };
-  fn.mockResolvedValueOnce = (val: any) => {
-    nextResults.push({ type: 'resolve', value: val });
-    return fn;
-  };
-  fn.mockRejectedValue = (val: any) => {
-    (fn as any)._defaultResult = { type: 'reject', value: val };
-    return fn;
-  };
-  fn.mockRejectedValueOnce = (val: any) => {
-    nextResults.push({ type: 'reject', value: val });
-    return fn;
-  };
-
-  // Override fn to use default result when nextResults is empty
   const wrappedFn: any = (...args: any[]) => {
     calls.push(args);
     const result = nextResults.shift();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/agent-runtime/test/OSSObjectStorageClient.test.ts` around lines 8 -
73, The code defines two implementations inside mockFn(): the unused local fn
and the returned wrappedFn, causing dead code; remove the entire first
implementation (the const fn = ... block and its mock* assignments that
reference fn) and keep only the returned wrappedFn implementation that uses
nextResults and calls, ensuring mockResolvedValue/mockRejectedValue and their
"...Once" variants are attached to wrappedFn and that any references to
_defaultResult remain on wrappedFn; update any internal references if necessary
so mockFn returns the single working wrappedFn.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tegg/core/agent-runtime/src/AgentRuntime.ts`:
- Around line 28-30: Update the local ESM imports in AgentRuntime by changing
the specifiers to use .js extensions instead of .ts for the imported modules:
replace imports for MessageConverter, RunBuilder, and SSEWriter so their
specifiers end with .js; ensure the same .js extension convention is applied
consistently for these symbols in other touched tegg TS files in this cohort.
- Around line 457-465: pushEvent currently uses blocking appendFileSync which
blocks the event loop when called from
executeStreamBackground/this.executor.execRun; change pushEvent to perform
non-blocking writes: either make pushEvent async and use fs.promises.appendFile,
or create and reuse a fs.createWriteStream on RunEventBuffer (e.g., a
buffer.stream or buffer.writeStream) and call write() with proper drain handling
to preserve event order and backpressure. Ensure you update call sites
(executeStreamBackground and any other callers) to await the async pushEvent or
handle write stream errors, and keep emitting buffer.emitter.emit('event',
event') immediately after scheduling the async write so consumers still receive
events in order.

In `@tegg/core/agent-runtime/src/OSSAgentStore.ts`:
- Line 13: The import in OSSAgentStore.ts is using a .ts extension which
violates ESM import-extension rules; update the import statement that brings in
dateBucket, newRunId, newThreadId, nowUnix, reverseMs from
'./AgentStoreUtils.ts' to use the .js extension (e.g., './AgentStoreUtils.js')
so the runtime can resolve the module correctly; ensure you update the import
only (preserve the named symbols) and run a quick build to confirm no other
imports use .ts extensions.
- Around line 88-116: writeThreadActivityIndex currently calls
this.client.put(...) directly so a synchronous throw escapes; wrap the call so
any synchronous exception becomes a rejected promise (e.g. create tracked via
Promise.resolve().then(() => this.client.put(indexKey, indexBody))) and then
attach the existing .catch(...) and .finally(...) handlers, and add tracked to
pendingIndexWrites; this ensures both sync and async failures are handled and
pendingIndexWrites.delete(tracked) always runs in finally.

In `@tegg/core/types/src/agent-runtime/AgentRuntime.ts`:
- Around line 1-2: Update the local ESM type import specifiers to use .js
extensions: in the imports that pull in AgentMessage, AgentRunConfig, and
RunStatus (the import statements shown in AgentRuntime.ts and AgentStore.ts),
change the source specifiers from './AgentMessage.ts' and './AgentStore.ts' (or
any other local .ts paths) to './AgentMessage.js' and './AgentStore.js'
respectively so they comply with the tegg ESM import-extension guideline.

---

Outside diff comments:
In `@tegg/core/agent-runtime/src/AgentRuntime.ts`:
- Around line 131-138: createThread forwards options?.metadata straight to
this.store.createThread allowing null/arrays to persist; call the existing
ensureThread validator to validate/normalize metadata before persisting. In
AgentRuntime.createThread, run const metadata = ensureThread(options?.metadata)
(or similar) and pass that validated metadata to this.store.createThread, and
use that same metadata in the returned ThreadObject so persisted and returned
thread metadata are object-shaped and consistent with ensureThread's rules.

---

Nitpick comments:
In `@tegg/core/agent-runtime/test/OSSObjectStorageClient.test.ts`:
- Around line 8-73: The code defines two implementations inside mockFn(): the
unused local fn and the returned wrappedFn, causing dead code; remove the entire
first implementation (the const fn = ... block and its mock* assignments that
reference fn) and keep only the returned wrappedFn implementation that uses
nextResults and calls, ensuring mockResolvedValue/mockRejectedValue and their
"...Once" variants are attached to wrappedFn and that any references to
_defaultResult remain on wrappedFn; update any internal references if necessary
so mockFn returns the single working wrappedFn.

In `@tegg/core/controller-decorator/src/decorator/agent/AgentController.ts`:
- Around line 17-21: Change the loose AgentRouteParam interface into a
discriminated union so parameter metadata is type-safe: replace the current
interface AgentRouteParam with a union of { index: number; type: 'body' } | {
index: number; type: 'pathParam'; name: string } | { index: number; type:
'query'; name: string }; then update all code that accesses AgentRouteParam
(notably the logic in AgentController that currently uses non-null assertions on
param.name) to rely on the discriminant (param.type) and remove the `!`
assertions, e.g., when param.type === 'pathParam' or 'query' safely access
param.name and when 'body' avoid using name. Ensure any helper functions or type
guards reflect the new union.
- Around line 34-53: createNotImplemented currently maps any paramCount >= 2 to
a 2-arg stub, which breaks the framework's param validation for routes with >2
params; update createNotImplemented to fail fast by throwing an Error when
paramCount > 2 (e.g. at the start of createNotImplemented check if paramCount >
2 and throw a clear error mentioning methodName and paramCount), then keep
existing branches for 0,1,2 parameters and continue to call
AgentInfoUtil.setNotImplemented(fn).

In `@tegg/plugin/controller/src/lib/AgentControllerObject.ts`:
- Line 267: The parseInt call in AgentControllerObject (where const seq =
parseInt(lastSeq as string, 10) || 0;) uses an unnecessary type assertion;
remove the "as string" and either call parseInt(lastSeq ?? undefined, 10) with
the existing || 0 fallback or explicitly handle undefined (e.g., const seq =
parseInt(lastSeq ?? '', 10) || 0) so the code is clearer—update the line in
AgentControllerObject accordingly without changing surrounding logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab0fddf1-6035-450e-a4a0-1db7f8ba9653

📥 Commits

Reviewing files that changed from the base of the PR and between 1836bce and 13c1446.

⛔ Files ignored due to path filters (1)
  • tegg/core/types/test/__snapshots__/index.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (26)
  • tegg/core/agent-runtime/src/AgentRuntime.ts
  • tegg/core/agent-runtime/src/AgentStoreUtils.ts
  • tegg/core/agent-runtime/src/HttpSSEWriter.ts
  • tegg/core/agent-runtime/src/MessageConverter.ts
  • tegg/core/agent-runtime/src/OSSAgentStore.ts
  • tegg/core/agent-runtime/src/RunBuilder.ts
  • tegg/core/agent-runtime/src/SSEWriter.ts
  • tegg/core/agent-runtime/test/AgentRuntime.metadata.test.ts
  • tegg/core/agent-runtime/test/AgentRuntime.test.ts
  • tegg/core/agent-runtime/test/AgentStoreUtils.test.ts
  • tegg/core/agent-runtime/test/HttpSSEWriter.test.ts
  • tegg/core/agent-runtime/test/MessageConverter.test.ts
  • tegg/core/agent-runtime/test/OSSAgentStore.test.ts
  • tegg/core/agent-runtime/test/OSSObjectStorageClient.test.ts
  • tegg/core/agent-runtime/test/RunBuilder.test.ts
  • tegg/core/agent-runtime/test/helpers.ts
  • tegg/core/controller-decorator/src/decorator/agent/AgentController.ts
  • tegg/core/controller-decorator/src/decorator/agent/AgentHandler.ts
  • tegg/core/controller-decorator/test/AgentController.test.ts
  • tegg/core/controller-decorator/test/fixtures/AgentFooController.ts
  • tegg/core/tegg/src/agent.ts
  • tegg/core/types/src/agent-runtime/AgentMessage.ts
  • tegg/core/types/src/agent-runtime/AgentRuntime.ts
  • tegg/core/types/src/agent-runtime/AgentStore.ts
  • tegg/core/types/src/agent-runtime/errors.ts
  • tegg/plugin/controller/src/lib/AgentControllerObject.ts

Comment on lines 28 to 30
import { MessageConverter } from './MessageConverter.ts';
import { RunBuilder } from './RunBuilder.ts';
import type { RunUsage } from './RunBuilder.ts';
import type { SSEWriter } from './SSEWriter.ts';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use .js specifiers for local ESM imports in tegg TS files.

These touched imports still use .ts extensions. Please switch them to .js here, and keep the same rule for the other changed tegg TS files in this cohort. As per coding guidelines, tegg/**/*.{ts,tsx} files require: “All imports should use .js extensions for ESM files”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/agent-runtime/src/AgentRuntime.ts` around lines 28 - 30, Update the
local ESM imports in AgentRuntime by changing the specifiers to use .js
extensions instead of .ts for the imported modules: replace imports for
MessageConverter, RunBuilder, and SSEWriter so their specifiers end with .js;
ensure the same .js extension convention is applied consistently for these
symbols in other touched tegg TS files in this cohort.

Source: Coding guidelines

Comment on lines +457 to +465
private pushEvent(buffer: RunEventBuffer, type: string, data: unknown): void {
const event: StreamEvent = {
seq: ++buffer.lastSeq,
type,
data,
ts: Date.now(),
};
appendFileSync(buffer.filePath, JSON.stringify(event) + '\n');
buffer.emitter.emit('event', event);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '457,505p' tegg/core/agent-runtime/src/AgentRuntime.ts

Repository: eggjs/egg

Length of output: 1735


Avoid synchronous fs writes in the per-message stream hot path.

pushEvent() uses appendFileSync(...), and executeStreamBackground() calls this.pushEvent(...) inside for await (const msg of this.executor.execRun(...)), so every yielded msg triggers a synchronous disk write. This blocks the Node event loop during token-heavy streaming and can throttle other concurrent work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/agent-runtime/src/AgentRuntime.ts` around lines 457 - 465,
pushEvent currently uses blocking appendFileSync which blocks the event loop
when called from executeStreamBackground/this.executor.execRun; change pushEvent
to perform non-blocking writes: either make pushEvent async and use
fs.promises.appendFile, or create and reuse a fs.createWriteStream on
RunEventBuffer (e.g., a buffer.stream or buffer.writeStream) and call write()
with proper drain handling to preserve event order and backpressure. Ensure you
update call sites (executeStreamBackground and any other callers) to await the
async pushEvent or handle write stream errors, and keep emitting
buffer.emitter.emit('event', event') immediately after scheduling the async
write so consumers still receive events in order.

import { AgentObjectType, RunStatus, AgentNotFoundError } from '@eggjs/tegg-types/agent-runtime';

import { nowUnix, newThreadId, newRunId } from './AgentStoreUtils.ts';
import { dateBucket, newRunId, newThreadId, nowUnix, reverseMs } from './AgentStoreUtils.ts';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use .js extension for the local ESM import.

./AgentStoreUtils.ts violates the tegg ESM import-extension rule and can break emitted-runtime resolution.

Suggested fix
-import { dateBucket, newRunId, newThreadId, nowUnix, reverseMs } from './AgentStoreUtils.ts';
+import { dateBucket, newRunId, newThreadId, nowUnix, reverseMs } from './AgentStoreUtils.js';

As per coding guidelines, “All imports should use .js extensions for ESM files”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { dateBucket, newRunId, newThreadId, nowUnix, reverseMs } from './AgentStoreUtils.ts';
import { dateBucket, newRunId, newThreadId, nowUnix, reverseMs } from './AgentStoreUtils.js';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/agent-runtime/src/OSSAgentStore.ts` at line 13, The import in
OSSAgentStore.ts is using a .ts extension which violates ESM import-extension
rules; update the import statement that brings in dateBucket, newRunId,
newThreadId, nowUnix, reverseMs from './AgentStoreUtils.ts' to use the .js
extension (e.g., './AgentStoreUtils.js') so the runtime can resolve the module
correctly; ensure you update the import only (preserve the named symbols) and
run a quick build to confirm no other imports use .ts extensions.

Source: Coding guidelines

Comment on lines +88 to +116
private writeThreadActivityIndex(
threadId: string,
createdAt: number,
updatedAtMs: number,
metadata: Record<string, unknown>,
): void {
const indexKey = this.threadActivityIndexKey(updatedAtMs, threadId);
const indexBody = JSON.stringify({
threadId,
createdAt,
updatedAt: Math.floor(updatedAtMs / 1000),
metadata,
});
const tracked: Promise<void> = this.client
.put(indexKey, indexBody)
.catch((err: unknown) => {
const errForLog: Error = err instanceof Error ? err : new Error(String(err));
this.logger.warn(
'[OSSAgentStore] failed to write thread activity index threadId=%s key=%s',
threadId,
indexKey,
errForLog,
);
})
.finally(() => {
this.pendingIndexWrites.delete(tracked);
});
this.pendingIndexWrites.add(tracked);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent best-effort index writes from leaking synchronous put() failures.

writeThreadActivityIndex only catches rejected promises. If client.put() throws before returning a promise, this path propagates and can fail create/update calls unexpectedly.

Suggested fix
-    const tracked: Promise<void> = this.client
-      .put(indexKey, indexBody)
+    const tracked: Promise<void> = (async () => {
+      await this.client.put(indexKey, indexBody);
+    })()
       .catch((err: unknown) => {
         const errForLog: Error = err instanceof Error ? err : new Error(String(err));
         this.logger.warn(
           '[OSSAgentStore] failed to write thread activity index threadId=%s key=%s',
           threadId,
           indexKey,
           errForLog,
         );
       })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/agent-runtime/src/OSSAgentStore.ts` around lines 88 - 116,
writeThreadActivityIndex currently calls this.client.put(...) directly so a
synchronous throw escapes; wrap the call so any synchronous exception becomes a
rejected promise (e.g. create tracked via Promise.resolve().then(() =>
this.client.put(indexKey, indexBody))) and then attach the existing .catch(...)
and .finally(...) handlers, and add tracked to pendingIndexWrites; this ensures
both sync and async failures are handled and pendingIndexWrites.delete(tracked)
always runs in finally.

Comment on lines +1 to +2
import type { AgentMessage } from './AgentMessage.ts';
import type { AgentRunConfig, RunStatus } from './AgentStore.ts';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify .ts import specifiers in touched files.
rg -nP --type=ts "from\\s+['\"][^'\"]+\\.ts['\"]" \
  tegg/core/types/src/agent-runtime/AgentRuntime.ts \
  tegg/core/types/src/agent-runtime/AgentStore.ts

# Inspect compiler/module settings relevant to extension handling.
fd -a 'tsconfig*.json' | while read -r f; do
  echo "== $f =="
  jq '.compilerOptions | {module, moduleResolution, allowImportingTsExtensions, verbatimModuleSyntax}' "$f"
done

Repository: eggjs/egg

Length of output: 838


Fix ESM local import specifiers in tegg core types to use .js instead of .ts.

tegg/core/types/src/agent-runtime/AgentRuntime.ts and tegg/core/types/src/agent-runtime/AgentStore.ts currently import local types with ./*.ts extensions, which violates the tegg ESM import-extension guideline.

Suggested patch
--- a/tegg/core/types/src/agent-runtime/AgentRuntime.ts
+++ b/tegg/core/types/src/agent-runtime/AgentRuntime.ts
@@
-import type { AgentMessage } from './AgentMessage.ts';
-import type { AgentRunConfig, RunStatus } from './AgentStore.ts';
+import type { AgentMessage } from './AgentMessage.js';
+import type { AgentRunConfig, RunStatus } from './AgentStore.js';
--- a/tegg/core/types/src/agent-runtime/AgentStore.ts
+++ b/tegg/core/types/src/agent-runtime/AgentStore.ts
@@
-import type { AgentMessage, InputMessage } from './AgentMessage.ts';
-import type { GetThreadOptions } from './AgentRuntime.ts';
+import type { AgentMessage, InputMessage } from './AgentMessage.js';
+import type { GetThreadOptions } from './AgentRuntime.js';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { AgentMessage } from './AgentMessage.ts';
import type { AgentRunConfig, RunStatus } from './AgentStore.ts';
import type { AgentMessage } from './AgentMessage.js';
import type { AgentRunConfig, RunStatus } from './AgentStore.js';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tegg/core/types/src/agent-runtime/AgentRuntime.ts` around lines 1 - 2, Update
the local ESM type import specifiers to use .js extensions: in the imports that
pull in AgentMessage, AgentRunConfig, and RunStatus (the import statements shown
in AgentRuntime.ts and AgentStore.ts), change the source specifiers from
'./AgentMessage.ts' and './AgentStore.ts' (or any other local .ts paths) to
'./AgentMessage.js' and './AgentStore.js' respectively so they comply with the
tegg ESM import-extension guideline.

Source: Coding guidelines

@jerryliang64 jerryliang64 merged commit 0333c73 into next Jun 11, 2026
26 checks passed
@jerryliang64 jerryliang64 deleted the sync/agent-controller-from-tegg branch June 11, 2026 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants