@ai-setting/roy-agent-cli 1.6.42 → 1.7.1

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.
@@ -9461,7 +9461,7 @@ class CliQuietModeService {
9461
9461
  // package.json
9462
9462
  var package_default = {
9463
9463
  name: "@ai-setting/roy-agent-cli",
9464
- version: "1.6.42",
9464
+ version: "1.7.1",
9465
9465
  type: "module",
9466
9466
  description: "CLI for roy-agent - Non-interactive command execution",
9467
9467
  main: "./dist/index.js",
@@ -9510,7 +9510,8 @@ var package_default = {
9510
9510
  "vscode-html-languageserver-bin": "^1.4.0",
9511
9511
  "vscode-json-languageserver": "^1.3.4",
9512
9512
  yargs: "^17.7.2",
9513
- "zod-to-json-schema": "^3.25.2"
9513
+ "zod-to-json-schema": "^3.25.2",
9514
+ "@earendil-works/pi-ai": "^0.74.2"
9514
9515
  },
9515
9516
  devDependencies: {
9516
9517
  "@types/node": "^20.10.0",
@@ -13380,6 +13381,9 @@ function createInteractiveCommand(externalEnvService) {
13380
13381
  showToolCalls: args.toolCalls,
13381
13382
  showToolResults: args.toolResults
13382
13383
  }, traceId, mergedContext);
13384
+ if (result && !queryExecutor.getFullText()) {
13385
+ console.log(result);
13386
+ }
13383
13387
  repl.setLastUsage(queryExecutor.getLastUsage());
13384
13388
  return result;
13385
13389
  },
@@ -13518,7 +13522,7 @@ import React6 from "react";
13518
13522
  import { render } from "ink";
13519
13523
 
13520
13524
  // src/RunTui.tsx
13521
- import { useState as useState6, useCallback as useCallback4, useRef as useRef2 } from "react";
13525
+ import { useState as useState6, useCallback as useCallback4, useRef as useRef2, useEffect as useEffect5 } from "react";
13522
13526
  import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
13523
13527
 
13524
13528
  // src/tui/App.tsx
@@ -13830,7 +13834,7 @@ function useStreamingAccumulator() {
13830
13834
  let snapshot = "";
13831
13835
  setStreamingText((prev) => {
13832
13836
  snapshot = prev;
13833
- return prev;
13837
+ return "";
13834
13838
  });
13835
13839
  return snapshot;
13836
13840
  }, []);
@@ -13839,7 +13843,7 @@ function useStreamingAccumulator() {
13839
13843
  let snapshot = "";
13840
13844
  setStreamingText((prev) => {
13841
13845
  snapshot = prev;
13842
- return prev;
13846
+ return "";
13843
13847
  });
13844
13848
  return snapshot;
13845
13849
  }, []);
@@ -13930,7 +13934,8 @@ var RunScreen = ({
13930
13934
  workdir,
13931
13935
  maxLines: initialMaxLines,
13932
13936
  markdownEnabled: markdownEnabled2,
13933
- onExit
13937
+ onExit,
13938
+ onRegisterHandlers
13934
13939
  }) => {
13935
13940
  const [history, setHistory] = useState6([]);
13936
13941
  const [errorMessage, setErrorMessage] = useState6(null);
@@ -13941,11 +13946,60 @@ var RunScreen = ({
13941
13946
  const { exit } = useApp2();
13942
13947
  const userTurnCountRef = useRef2(0);
13943
13948
  const assistantTurnCountRef = useRef2(0);
13949
+ const isProcessingRef = useRef2(0);
13950
+ const executeQueryRef = useRef2(null);
13944
13951
  const width = process.stdout.columns ?? 80;
13945
13952
  const doExit = useCallback4(() => {
13946
13953
  onExit?.();
13947
13954
  exit();
13948
13955
  }, [onExit, exit]);
13956
+ const executeQuery = useCallback4(async (message) => {
13957
+ isProcessingRef.current += 1;
13958
+ stream.start();
13959
+ try {
13960
+ let responseText;
13961
+ if (streamHandleQuery) {
13962
+ responseText = await streamHandleQuery(message, (chunk) => stream.append(chunk));
13963
+ } else {
13964
+ responseText = await handleQuery(message);
13965
+ }
13966
+ const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
13967
+ const finalText = truncateForTerminal(rendered, maxLines);
13968
+ stream.finish();
13969
+ setHistory((h) => [...h, { role: "assistant", content: finalText }]);
13970
+ assistantTurnCountRef.current += 1;
13971
+ } catch (err) {
13972
+ const msg = err instanceof Error ? err.message : String(err);
13973
+ stream.fail(err instanceof Error ? err : new Error(msg));
13974
+ setErrorMessage(msg);
13975
+ setHistory((h) => [
13976
+ ...h,
13977
+ { role: "tool", content: `❌ Error: ${msg}` }
13978
+ ]);
13979
+ } finally {
13980
+ isProcessingRef.current -= 1;
13981
+ }
13982
+ }, [handleQuery, markdownEnabled2, maxLines, stream, streamHandleQuery, width]);
13983
+ useEffect5(() => {
13984
+ executeQueryRef.current = executeQuery;
13985
+ }, [executeQuery]);
13986
+ useEffect5(() => {
13987
+ if (!onRegisterHandlers)
13988
+ return;
13989
+ const handleNotification = async (message, _envEvent) => {
13990
+ setHistory((h) => [
13991
+ ...h,
13992
+ { role: "tool", content: `\uD83D\uDCE2 ${message}` }
13993
+ ]);
13994
+ if (executeQueryRef.current) {
13995
+ await executeQueryRef.current(message);
13996
+ }
13997
+ };
13998
+ const isIdle = () => {
13999
+ return isProcessingRef.current === 0;
14000
+ };
14001
+ onRegisterHandlers({ onNotification: handleNotification, isIdle });
14002
+ }, [onRegisterHandlers]);
13949
14003
  const handleSubmit = useCallback4(async (raw) => {
13950
14004
  const trimmed = raw.trim();
13951
14005
  if (!trimmed)
@@ -14007,38 +14061,14 @@ var RunScreen = ({
14007
14061
  setHistory((h) => [...h, { role: "user", content: trimmed }]);
14008
14062
  setUserHistory((uh) => [trimmed, ...uh]);
14009
14063
  userTurnCountRef.current += 1;
14010
- stream.start();
14011
- try {
14012
- let responseText;
14013
- if (streamHandleQuery) {
14014
- responseText = await streamHandleQuery(trimmed, (chunk) => stream.append(chunk));
14015
- } else {
14016
- responseText = await handleQuery(trimmed);
14017
- }
14018
- const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
14019
- const finalText = truncateForTerminal(rendered, maxLines);
14020
- stream.finish();
14021
- setHistory((h) => [...h, { role: "assistant", content: finalText }]);
14022
- assistantTurnCountRef.current += 1;
14023
- } catch (err) {
14024
- const msg = err instanceof Error ? err.message : String(err);
14025
- stream.fail(err instanceof Error ? err : new Error(msg));
14026
- setErrorMessage(msg);
14027
- setHistory((h) => [
14028
- ...h,
14029
- { role: "tool", content: `❌ Error: ${msg}` }
14030
- ]);
14031
- }
14064
+ await executeQuery(trimmed);
14032
14065
  }, [
14033
14066
  doExit,
14034
14067
  exit,
14035
- handleQuery,
14036
- markdownEnabled2,
14068
+ executeQuery,
14037
14069
  maxLines,
14038
14070
  model,
14039
- stream,
14040
- streamHandleQuery,
14041
- width
14071
+ workdir
14042
14072
  ]);
14043
14073
  const handleCancel = useCallback4(() => {
14044
14074
  doExit();
@@ -14081,7 +14111,7 @@ var RunTui_default = RunScreen;
14081
14111
 
14082
14112
  // src/commands/run.ts
14083
14113
  init_markdown();
14084
- async function loadHandleQuery(options) {
14114
+ async function loadHandleQuery(options, hooks) {
14085
14115
  const quiet = CliQuietModeService.getInstance().isQuiet();
14086
14116
  const output = new OutputService2;
14087
14117
  output.configure({ quiet });
@@ -14098,10 +14128,126 @@ async function loadHandleQuery(options) {
14098
14128
  if (!env) {
14099
14129
  throw new Error("Failed to create environment");
14100
14130
  }
14101
- return async (message) => {
14102
- const result = await env.handle_query(message, {
14103
- sessionId: "cli-run"
14131
+ const sessionComponent = envService.getSession();
14132
+ if (!sessionComponent) {
14133
+ throw new Error("Failed to create session component");
14134
+ }
14135
+ const sessionManager = new SessionManager({
14136
+ sessionComponent,
14137
+ output,
14138
+ quiet: true
14139
+ });
14140
+ const sessionResult = await sessionManager.init(options.session, options.continue);
14141
+ const sessionId = sessionResult.sessionId;
14142
+ const queryExecutor = new QueryExecutor({
14143
+ env,
14144
+ sessionComponent,
14145
+ output,
14146
+ quiet: true,
14147
+ silent: true
14148
+ });
14149
+ let agentContext = undefined;
14150
+ if (options.agent && options.agent !== "default") {
14151
+ const agentComponent = env.getComponent("agent");
14152
+ const validation = validateActAgent(agentComponent, options.agent);
14153
+ if (!validation.valid) {
14154
+ output.error(validation.message);
14155
+ output.info("提示: roy-agent agents list");
14156
+ process.exit(1);
14157
+ }
14158
+ agentContext = buildActAgentContext(options.agent);
14159
+ }
14160
+ let eventSourceSystemPrompt;
14161
+ const rawEventSourceIds = options.eventSource;
14162
+ const eventSourceIds = Array.isArray(rawEventSourceIds) ? rawEventSourceIds.filter((s) => !!s) : rawEventSourceIds ? [rawEventSourceIds] : [];
14163
+ if (eventSourceIds.length > 0) {
14164
+ const eventSourceComponent = env.getComponent("event-source");
14165
+ if (!eventSourceComponent || typeof eventSourceComponent.startSource !== "function") {
14166
+ output.error("❌ 未找到事件源组件或 startSource 方法");
14167
+ output.info("请确保 EventSourceComponent 已正确注册");
14168
+ process.exit(1);
14169
+ }
14170
+ const summary = await startEventSources(eventSourceIds, eventSourceComponent, output);
14171
+ const preloadedPrompts = collectEventSourceSystemPrompts(eventSourceIds, eventSourceComponent);
14172
+ eventSourceSystemPrompt = preloadedPrompts.length > 0 ? joinResolvedSystemPrompts(preloadedPrompts) : undefined;
14173
+ if (eventSourceIds.length === 1 && summary.failedCount > 0) {
14174
+ process.exit(1);
14175
+ }
14176
+ }
14177
+ const llmComponent = envService.getLLM();
14178
+ queryExecutor.initStreamOutput(llmComponent ?? null);
14179
+ const handleQuery = async (message) => {
14180
+ const mergedContext = {
14181
+ ...agentContext,
14182
+ model: options.model,
14183
+ eventSourceSystemPrompt,
14184
+ metadata: {
14185
+ ...agentContext?.metadata ?? {},
14186
+ defaultAgentName: options.defaultAgentName
14187
+ }
14188
+ };
14189
+ const result = await queryExecutor.execute(message, sessionId, {
14190
+ showReasoning: options.reasoning ?? true,
14191
+ showToolCalls: options.toolCalls ?? true,
14192
+ showToolResults: options.toolResults ?? false
14193
+ }, undefined, mergedContext);
14194
+ return String(result ?? "");
14195
+ };
14196
+ const taskEventHandler = new TaskEventHandler({
14197
+ env,
14198
+ enabled: true
14199
+ });
14200
+ const eventHandler = new EventHandler({
14201
+ env,
14202
+ eventTypes: ["task.background.*", "event-source.event.*"],
14203
+ formatter: new EventMessageFormatter({ prefix: "[通知]" }),
14204
+ isIdle: hooks?.isIdle ?? (() => true),
14205
+ sessionComponent,
14206
+ onEvent: async (_message) => {},
14207
+ onEventWithEnv: async (formattedEvent) => {
14208
+ if (hooks?.onNotification) {
14209
+ await hooks.onNotification(formattedEvent.message, formattedEvent.envEvent);
14210
+ }
14211
+ }
14212
+ });
14213
+ return {
14214
+ handleQuery,
14215
+ envService,
14216
+ env,
14217
+ sessionId,
14218
+ queryExecutor,
14219
+ agentContext,
14220
+ eventSourceSystemPrompt,
14221
+ sessionComponent,
14222
+ eventHandler,
14223
+ taskEventHandler
14224
+ };
14225
+ }
14226
+ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext, eventSourceSystemPrompt) {
14227
+ return async (message, onChunk) => {
14228
+ queryExecutor.setChunkCallback((event) => {
14229
+ if (event.type === "llm.text") {
14230
+ if (event.delta) {
14231
+ onChunk(event.delta);
14232
+ } else if (event.content) {
14233
+ onChunk(event.content);
14234
+ }
14235
+ }
14104
14236
  });
14237
+ const mergedContext = {
14238
+ ...agentContext,
14239
+ model: options.model,
14240
+ eventSourceSystemPrompt,
14241
+ metadata: {
14242
+ ...agentContext?.metadata ?? {},
14243
+ defaultAgentName: options.defaultAgentName
14244
+ }
14245
+ };
14246
+ const result = await queryExecutor.execute(message, sessionId, {
14247
+ showReasoning: options.reasoning ?? true,
14248
+ showToolCalls: options.toolCalls ?? true,
14249
+ showToolResults: options.toolResults ?? false
14250
+ }, undefined, mergedContext);
14105
14251
  return String(result ?? "");
14106
14252
  };
14107
14253
  }
@@ -14141,6 +14287,41 @@ function createRunCommand() {
14141
14287
  }).option("env", {
14142
14288
  type: "string",
14143
14289
  describe: "Environment name"
14290
+ }).option("session", {
14291
+ type: "string",
14292
+ describe: "指定会话 ID(注: -s 已被 --system-prompt 占用)"
14293
+ }).option("continue", {
14294
+ alias: "c",
14295
+ type: "boolean",
14296
+ describe: "继续上次会话",
14297
+ default: false
14298
+ }).option("reasoning", {
14299
+ alias: "r",
14300
+ type: "boolean",
14301
+ describe: "显示 AI 思考过程",
14302
+ default: true
14303
+ }).option("tool-calls", {
14304
+ type: "boolean",
14305
+ describe: "显示工具调用",
14306
+ default: true
14307
+ }).option("tool-results", {
14308
+ type: "boolean",
14309
+ describe: "显示工具执行结果",
14310
+ default: false
14311
+ }).option("event-source", {
14312
+ alias: "es",
14313
+ type: "array",
14314
+ string: true,
14315
+ describe: "启动时一并启动的事件源 ID(可多次指定,支持 --es a --es b)"
14316
+ }).option("agent", {
14317
+ alias: "a",
14318
+ type: "string",
14319
+ describe: "指定使用的 agent (如 explore, general)"
14320
+ }).option("default-agent-name", {
14321
+ alias: "dan",
14322
+ type: "string",
14323
+ describe: "Default agent 名字(默认 Roy,可被覆盖)",
14324
+ default: "Roy"
14144
14325
  }),
14145
14326
  handler: async (argv) => {
14146
14327
  if (argv.markdown === false) {
@@ -14149,18 +14330,44 @@ function createRunCommand() {
14149
14330
  setMarkdownEnabled(true);
14150
14331
  }
14151
14332
  const maxLines = Number.parseInt(argv.maxLines ?? "500", 10);
14152
- const handleQuery = await loadHandleQuery(argv);
14333
+ const runState = {
14334
+ isIdle: () => true,
14335
+ onNotification: () => {}
14336
+ };
14337
+ const {
14338
+ handleQuery,
14339
+ queryExecutor,
14340
+ sessionId,
14341
+ agentContext,
14342
+ eventSourceSystemPrompt,
14343
+ eventHandler,
14344
+ taskEventHandler
14345
+ } = await loadHandleQuery(argv, {
14346
+ isIdle: () => runState.isIdle(),
14347
+ onNotification: (msg, envEvent) => runState.onNotification(msg, envEvent)
14348
+ });
14349
+ const streamHandleQuery = createStreamHandleQuery(queryExecutor, sessionId, argv, agentContext, eventSourceSystemPrompt);
14350
+ const stopEventHandler = eventHandler.start();
14351
+ const stopTaskEventHandler = taskEventHandler.start();
14153
14352
  const { waitUntilExit } = render(React6.createElement(RunTui_default, {
14154
14353
  handleQuery,
14354
+ streamHandleQuery,
14155
14355
  model: argv.model,
14156
14356
  workdir: argv.workdir,
14157
14357
  maxLines,
14158
14358
  markdownEnabled: argv.markdown !== false,
14359
+ onRegisterHandlers: (handlers) => {
14360
+ runState.onNotification = handlers.onNotification;
14361
+ runState.isIdle = handlers.isIdle;
14362
+ },
14159
14363
  onExit: () => {}
14160
14364
  }), { exitOnCtrlC: true });
14161
14365
  try {
14162
14366
  await waitUntilExit();
14163
14367
  } finally {
14368
+ stopEventHandler();
14369
+ stopTaskEventHandler();
14370
+ queryExecutor.dispose();
14164
14371
  process.exit(0);
14165
14372
  }
14166
14373
  }
@@ -14173,7 +14380,7 @@ import React8 from "react";
14173
14380
  import { render as render2 } from "ink";
14174
14381
 
14175
14382
  // src/ChatTui.tsx
14176
- import { useEffect as useEffect5, useState as useState7 } from "react";
14383
+ import { useEffect as useEffect6, useState as useState7 } from "react";
14177
14384
  import { Box as Box7, useApp as useApp3 } from "ink";
14178
14385
  init_markdown();
14179
14386
  import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
@@ -14192,7 +14399,7 @@ var ChatScreen = ({
14192
14399
  const stream = useStreamingAccumulator();
14193
14400
  const { exit } = useApp3();
14194
14401
  const width = process.stdout.columns ?? 80;
14195
- useEffect5(() => {
14402
+ useEffect6(() => {
14196
14403
  let cancelled = false;
14197
14404
  stream.start();
14198
14405
  (async () => {
package/dist/index.js CHANGED
@@ -9460,7 +9460,7 @@ class CliQuietModeService {
9460
9460
  // package.json
9461
9461
  var package_default = {
9462
9462
  name: "@ai-setting/roy-agent-cli",
9463
- version: "1.6.42",
9463
+ version: "1.7.1",
9464
9464
  type: "module",
9465
9465
  description: "CLI for roy-agent - Non-interactive command execution",
9466
9466
  main: "./dist/index.js",
@@ -9509,7 +9509,8 @@ var package_default = {
9509
9509
  "vscode-html-languageserver-bin": "^1.4.0",
9510
9510
  "vscode-json-languageserver": "^1.3.4",
9511
9511
  yargs: "^17.7.2",
9512
- "zod-to-json-schema": "^3.25.2"
9512
+ "zod-to-json-schema": "^3.25.2",
9513
+ "@earendil-works/pi-ai": "^0.74.2"
9513
9514
  },
9514
9515
  devDependencies: {
9515
9516
  "@types/node": "^20.10.0",
@@ -13379,6 +13380,9 @@ function createInteractiveCommand(externalEnvService) {
13379
13380
  showToolCalls: args.toolCalls,
13380
13381
  showToolResults: args.toolResults
13381
13382
  }, traceId, mergedContext);
13383
+ if (result && !queryExecutor.getFullText()) {
13384
+ console.log(result);
13385
+ }
13382
13386
  repl.setLastUsage(queryExecutor.getLastUsage());
13383
13387
  return result;
13384
13388
  },
@@ -13517,7 +13521,7 @@ import React6 from "react";
13517
13521
  import { render } from "ink";
13518
13522
 
13519
13523
  // src/RunTui.tsx
13520
- import { useState as useState6, useCallback as useCallback4, useRef as useRef2 } from "react";
13524
+ import { useState as useState6, useCallback as useCallback4, useRef as useRef2, useEffect as useEffect5 } from "react";
13521
13525
  import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
13522
13526
 
13523
13527
  // src/tui/App.tsx
@@ -13829,7 +13833,7 @@ function useStreamingAccumulator() {
13829
13833
  let snapshot = "";
13830
13834
  setStreamingText((prev) => {
13831
13835
  snapshot = prev;
13832
- return prev;
13836
+ return "";
13833
13837
  });
13834
13838
  return snapshot;
13835
13839
  }, []);
@@ -13838,7 +13842,7 @@ function useStreamingAccumulator() {
13838
13842
  let snapshot = "";
13839
13843
  setStreamingText((prev) => {
13840
13844
  snapshot = prev;
13841
- return prev;
13845
+ return "";
13842
13846
  });
13843
13847
  return snapshot;
13844
13848
  }, []);
@@ -13929,7 +13933,8 @@ var RunScreen = ({
13929
13933
  workdir,
13930
13934
  maxLines: initialMaxLines,
13931
13935
  markdownEnabled: markdownEnabled2,
13932
- onExit
13936
+ onExit,
13937
+ onRegisterHandlers
13933
13938
  }) => {
13934
13939
  const [history, setHistory] = useState6([]);
13935
13940
  const [errorMessage, setErrorMessage] = useState6(null);
@@ -13940,11 +13945,60 @@ var RunScreen = ({
13940
13945
  const { exit } = useApp2();
13941
13946
  const userTurnCountRef = useRef2(0);
13942
13947
  const assistantTurnCountRef = useRef2(0);
13948
+ const isProcessingRef = useRef2(0);
13949
+ const executeQueryRef = useRef2(null);
13943
13950
  const width = process.stdout.columns ?? 80;
13944
13951
  const doExit = useCallback4(() => {
13945
13952
  onExit?.();
13946
13953
  exit();
13947
13954
  }, [onExit, exit]);
13955
+ const executeQuery = useCallback4(async (message) => {
13956
+ isProcessingRef.current += 1;
13957
+ stream.start();
13958
+ try {
13959
+ let responseText;
13960
+ if (streamHandleQuery) {
13961
+ responseText = await streamHandleQuery(message, (chunk) => stream.append(chunk));
13962
+ } else {
13963
+ responseText = await handleQuery(message);
13964
+ }
13965
+ const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
13966
+ const finalText = truncateForTerminal(rendered, maxLines);
13967
+ stream.finish();
13968
+ setHistory((h) => [...h, { role: "assistant", content: finalText }]);
13969
+ assistantTurnCountRef.current += 1;
13970
+ } catch (err) {
13971
+ const msg = err instanceof Error ? err.message : String(err);
13972
+ stream.fail(err instanceof Error ? err : new Error(msg));
13973
+ setErrorMessage(msg);
13974
+ setHistory((h) => [
13975
+ ...h,
13976
+ { role: "tool", content: `❌ Error: ${msg}` }
13977
+ ]);
13978
+ } finally {
13979
+ isProcessingRef.current -= 1;
13980
+ }
13981
+ }, [handleQuery, markdownEnabled2, maxLines, stream, streamHandleQuery, width]);
13982
+ useEffect5(() => {
13983
+ executeQueryRef.current = executeQuery;
13984
+ }, [executeQuery]);
13985
+ useEffect5(() => {
13986
+ if (!onRegisterHandlers)
13987
+ return;
13988
+ const handleNotification = async (message, _envEvent) => {
13989
+ setHistory((h) => [
13990
+ ...h,
13991
+ { role: "tool", content: `\uD83D\uDCE2 ${message}` }
13992
+ ]);
13993
+ if (executeQueryRef.current) {
13994
+ await executeQueryRef.current(message);
13995
+ }
13996
+ };
13997
+ const isIdle = () => {
13998
+ return isProcessingRef.current === 0;
13999
+ };
14000
+ onRegisterHandlers({ onNotification: handleNotification, isIdle });
14001
+ }, [onRegisterHandlers]);
13948
14002
  const handleSubmit = useCallback4(async (raw) => {
13949
14003
  const trimmed = raw.trim();
13950
14004
  if (!trimmed)
@@ -14006,38 +14060,14 @@ var RunScreen = ({
14006
14060
  setHistory((h) => [...h, { role: "user", content: trimmed }]);
14007
14061
  setUserHistory((uh) => [trimmed, ...uh]);
14008
14062
  userTurnCountRef.current += 1;
14009
- stream.start();
14010
- try {
14011
- let responseText;
14012
- if (streamHandleQuery) {
14013
- responseText = await streamHandleQuery(trimmed, (chunk) => stream.append(chunk));
14014
- } else {
14015
- responseText = await handleQuery(trimmed);
14016
- }
14017
- const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
14018
- const finalText = truncateForTerminal(rendered, maxLines);
14019
- stream.finish();
14020
- setHistory((h) => [...h, { role: "assistant", content: finalText }]);
14021
- assistantTurnCountRef.current += 1;
14022
- } catch (err) {
14023
- const msg = err instanceof Error ? err.message : String(err);
14024
- stream.fail(err instanceof Error ? err : new Error(msg));
14025
- setErrorMessage(msg);
14026
- setHistory((h) => [
14027
- ...h,
14028
- { role: "tool", content: `❌ Error: ${msg}` }
14029
- ]);
14030
- }
14063
+ await executeQuery(trimmed);
14031
14064
  }, [
14032
14065
  doExit,
14033
14066
  exit,
14034
- handleQuery,
14035
- markdownEnabled2,
14067
+ executeQuery,
14036
14068
  maxLines,
14037
14069
  model,
14038
- stream,
14039
- streamHandleQuery,
14040
- width
14070
+ workdir
14041
14071
  ]);
14042
14072
  const handleCancel = useCallback4(() => {
14043
14073
  doExit();
@@ -14080,7 +14110,7 @@ var RunTui_default = RunScreen;
14080
14110
 
14081
14111
  // src/commands/run.ts
14082
14112
  init_markdown();
14083
- async function loadHandleQuery(options) {
14113
+ async function loadHandleQuery(options, hooks) {
14084
14114
  const quiet = CliQuietModeService.getInstance().isQuiet();
14085
14115
  const output = new OutputService2;
14086
14116
  output.configure({ quiet });
@@ -14097,10 +14127,126 @@ async function loadHandleQuery(options) {
14097
14127
  if (!env) {
14098
14128
  throw new Error("Failed to create environment");
14099
14129
  }
14100
- return async (message) => {
14101
- const result = await env.handle_query(message, {
14102
- sessionId: "cli-run"
14130
+ const sessionComponent = envService.getSession();
14131
+ if (!sessionComponent) {
14132
+ throw new Error("Failed to create session component");
14133
+ }
14134
+ const sessionManager = new SessionManager({
14135
+ sessionComponent,
14136
+ output,
14137
+ quiet: true
14138
+ });
14139
+ const sessionResult = await sessionManager.init(options.session, options.continue);
14140
+ const sessionId = sessionResult.sessionId;
14141
+ const queryExecutor = new QueryExecutor({
14142
+ env,
14143
+ sessionComponent,
14144
+ output,
14145
+ quiet: true,
14146
+ silent: true
14147
+ });
14148
+ let agentContext = undefined;
14149
+ if (options.agent && options.agent !== "default") {
14150
+ const agentComponent = env.getComponent("agent");
14151
+ const validation = validateActAgent(agentComponent, options.agent);
14152
+ if (!validation.valid) {
14153
+ output.error(validation.message);
14154
+ output.info("提示: roy-agent agents list");
14155
+ process.exit(1);
14156
+ }
14157
+ agentContext = buildActAgentContext(options.agent);
14158
+ }
14159
+ let eventSourceSystemPrompt;
14160
+ const rawEventSourceIds = options.eventSource;
14161
+ const eventSourceIds = Array.isArray(rawEventSourceIds) ? rawEventSourceIds.filter((s) => !!s) : rawEventSourceIds ? [rawEventSourceIds] : [];
14162
+ if (eventSourceIds.length > 0) {
14163
+ const eventSourceComponent = env.getComponent("event-source");
14164
+ if (!eventSourceComponent || typeof eventSourceComponent.startSource !== "function") {
14165
+ output.error("❌ 未找到事件源组件或 startSource 方法");
14166
+ output.info("请确保 EventSourceComponent 已正确注册");
14167
+ process.exit(1);
14168
+ }
14169
+ const summary = await startEventSources(eventSourceIds, eventSourceComponent, output);
14170
+ const preloadedPrompts = collectEventSourceSystemPrompts(eventSourceIds, eventSourceComponent);
14171
+ eventSourceSystemPrompt = preloadedPrompts.length > 0 ? joinResolvedSystemPrompts(preloadedPrompts) : undefined;
14172
+ if (eventSourceIds.length === 1 && summary.failedCount > 0) {
14173
+ process.exit(1);
14174
+ }
14175
+ }
14176
+ const llmComponent = envService.getLLM();
14177
+ queryExecutor.initStreamOutput(llmComponent ?? null);
14178
+ const handleQuery = async (message) => {
14179
+ const mergedContext = {
14180
+ ...agentContext,
14181
+ model: options.model,
14182
+ eventSourceSystemPrompt,
14183
+ metadata: {
14184
+ ...agentContext?.metadata ?? {},
14185
+ defaultAgentName: options.defaultAgentName
14186
+ }
14187
+ };
14188
+ const result = await queryExecutor.execute(message, sessionId, {
14189
+ showReasoning: options.reasoning ?? true,
14190
+ showToolCalls: options.toolCalls ?? true,
14191
+ showToolResults: options.toolResults ?? false
14192
+ }, undefined, mergedContext);
14193
+ return String(result ?? "");
14194
+ };
14195
+ const taskEventHandler = new TaskEventHandler({
14196
+ env,
14197
+ enabled: true
14198
+ });
14199
+ const eventHandler = new EventHandler({
14200
+ env,
14201
+ eventTypes: ["task.background.*", "event-source.event.*"],
14202
+ formatter: new EventMessageFormatter({ prefix: "[通知]" }),
14203
+ isIdle: hooks?.isIdle ?? (() => true),
14204
+ sessionComponent,
14205
+ onEvent: async (_message) => {},
14206
+ onEventWithEnv: async (formattedEvent) => {
14207
+ if (hooks?.onNotification) {
14208
+ await hooks.onNotification(formattedEvent.message, formattedEvent.envEvent);
14209
+ }
14210
+ }
14211
+ });
14212
+ return {
14213
+ handleQuery,
14214
+ envService,
14215
+ env,
14216
+ sessionId,
14217
+ queryExecutor,
14218
+ agentContext,
14219
+ eventSourceSystemPrompt,
14220
+ sessionComponent,
14221
+ eventHandler,
14222
+ taskEventHandler
14223
+ };
14224
+ }
14225
+ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext, eventSourceSystemPrompt) {
14226
+ return async (message, onChunk) => {
14227
+ queryExecutor.setChunkCallback((event) => {
14228
+ if (event.type === "llm.text") {
14229
+ if (event.delta) {
14230
+ onChunk(event.delta);
14231
+ } else if (event.content) {
14232
+ onChunk(event.content);
14233
+ }
14234
+ }
14103
14235
  });
14236
+ const mergedContext = {
14237
+ ...agentContext,
14238
+ model: options.model,
14239
+ eventSourceSystemPrompt,
14240
+ metadata: {
14241
+ ...agentContext?.metadata ?? {},
14242
+ defaultAgentName: options.defaultAgentName
14243
+ }
14244
+ };
14245
+ const result = await queryExecutor.execute(message, sessionId, {
14246
+ showReasoning: options.reasoning ?? true,
14247
+ showToolCalls: options.toolCalls ?? true,
14248
+ showToolResults: options.toolResults ?? false
14249
+ }, undefined, mergedContext);
14104
14250
  return String(result ?? "");
14105
14251
  };
14106
14252
  }
@@ -14140,6 +14286,41 @@ function createRunCommand() {
14140
14286
  }).option("env", {
14141
14287
  type: "string",
14142
14288
  describe: "Environment name"
14289
+ }).option("session", {
14290
+ type: "string",
14291
+ describe: "指定会话 ID(注: -s 已被 --system-prompt 占用)"
14292
+ }).option("continue", {
14293
+ alias: "c",
14294
+ type: "boolean",
14295
+ describe: "继续上次会话",
14296
+ default: false
14297
+ }).option("reasoning", {
14298
+ alias: "r",
14299
+ type: "boolean",
14300
+ describe: "显示 AI 思考过程",
14301
+ default: true
14302
+ }).option("tool-calls", {
14303
+ type: "boolean",
14304
+ describe: "显示工具调用",
14305
+ default: true
14306
+ }).option("tool-results", {
14307
+ type: "boolean",
14308
+ describe: "显示工具执行结果",
14309
+ default: false
14310
+ }).option("event-source", {
14311
+ alias: "es",
14312
+ type: "array",
14313
+ string: true,
14314
+ describe: "启动时一并启动的事件源 ID(可多次指定,支持 --es a --es b)"
14315
+ }).option("agent", {
14316
+ alias: "a",
14317
+ type: "string",
14318
+ describe: "指定使用的 agent (如 explore, general)"
14319
+ }).option("default-agent-name", {
14320
+ alias: "dan",
14321
+ type: "string",
14322
+ describe: "Default agent 名字(默认 Roy,可被覆盖)",
14323
+ default: "Roy"
14143
14324
  }),
14144
14325
  handler: async (argv) => {
14145
14326
  if (argv.markdown === false) {
@@ -14148,18 +14329,44 @@ function createRunCommand() {
14148
14329
  setMarkdownEnabled(true);
14149
14330
  }
14150
14331
  const maxLines = Number.parseInt(argv.maxLines ?? "500", 10);
14151
- const handleQuery = await loadHandleQuery(argv);
14332
+ const runState = {
14333
+ isIdle: () => true,
14334
+ onNotification: () => {}
14335
+ };
14336
+ const {
14337
+ handleQuery,
14338
+ queryExecutor,
14339
+ sessionId,
14340
+ agentContext,
14341
+ eventSourceSystemPrompt,
14342
+ eventHandler,
14343
+ taskEventHandler
14344
+ } = await loadHandleQuery(argv, {
14345
+ isIdle: () => runState.isIdle(),
14346
+ onNotification: (msg, envEvent) => runState.onNotification(msg, envEvent)
14347
+ });
14348
+ const streamHandleQuery = createStreamHandleQuery(queryExecutor, sessionId, argv, agentContext, eventSourceSystemPrompt);
14349
+ const stopEventHandler = eventHandler.start();
14350
+ const stopTaskEventHandler = taskEventHandler.start();
14152
14351
  const { waitUntilExit } = render(React6.createElement(RunTui_default, {
14153
14352
  handleQuery,
14353
+ streamHandleQuery,
14154
14354
  model: argv.model,
14155
14355
  workdir: argv.workdir,
14156
14356
  maxLines,
14157
14357
  markdownEnabled: argv.markdown !== false,
14358
+ onRegisterHandlers: (handlers) => {
14359
+ runState.onNotification = handlers.onNotification;
14360
+ runState.isIdle = handlers.isIdle;
14361
+ },
14158
14362
  onExit: () => {}
14159
14363
  }), { exitOnCtrlC: true });
14160
14364
  try {
14161
14365
  await waitUntilExit();
14162
14366
  } finally {
14367
+ stopEventHandler();
14368
+ stopTaskEventHandler();
14369
+ queryExecutor.dispose();
14163
14370
  process.exit(0);
14164
14371
  }
14165
14372
  }
@@ -14172,7 +14379,7 @@ import React8 from "react";
14172
14379
  import { render as render2 } from "ink";
14173
14380
 
14174
14381
  // src/ChatTui.tsx
14175
- import { useEffect as useEffect5, useState as useState7 } from "react";
14382
+ import { useEffect as useEffect6, useState as useState7 } from "react";
14176
14383
  import { Box as Box7, useApp as useApp3 } from "ink";
14177
14384
  init_markdown();
14178
14385
  import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
@@ -14191,7 +14398,7 @@ var ChatScreen = ({
14191
14398
  const stream = useStreamingAccumulator();
14192
14399
  const { exit } = useApp3();
14193
14400
  const width = process.stdout.columns ?? 80;
14194
- useEffect5(() => {
14401
+ useEffect6(() => {
14195
14402
  let cancelled = false;
14196
14403
  stream.start();
14197
14404
  (async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-agent-cli",
3
- "version": "1.6.42",
3
+ "version": "1.7.1",
4
4
  "type": "module",
5
5
  "description": "CLI for roy-agent - Non-interactive command execution",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,8 @@
49
49
  "vscode-html-languageserver-bin": "^1.4.0",
50
50
  "vscode-json-languageserver": "^1.3.4",
51
51
  "yargs": "^17.7.2",
52
- "zod-to-json-schema": "^3.25.2"
52
+ "zod-to-json-schema": "^3.25.2",
53
+ "@earendil-works/pi-ai": "^0.74.2"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@types/node": "^20.10.0",