@kin-tio/cli 0.7.0 → 0.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ This file records important user-visible changes after the first public release.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.7.1
8
+
9
+ - Made `kintio ilink start` use the managed background daemon by default while
10
+ retaining `--foreground` for external service managers, simplified
11
+ `kintio ilink list` to reusable provider account IDs, rejected Node.js below
12
+ 24 before application startup, and added sanitized Codex request diagnostics
13
+ ([#65](https://github.com/Gkxie/kintio/issues/65)).
14
+
7
15
  ## 0.7.0
8
16
 
9
17
  - Added `kintio ilink login`, which reuses the iLink enrollment state machine
package/README.md CHANGED
@@ -78,7 +78,8 @@ kintio ilink start
78
78
  ```
79
79
 
80
80
  `ilink login` performs one encrypted enrollment, starts no listener, and exits. `ilink start` then runs provider
81
- polling and the host Agent in the foreground without Hono or a TCP listener. Both commands
81
+ polling and the host Agent through the background daemon without Hono or a TCP listener.
82
+ Use `--foreground` only when a service manager needs to own the process. Both commands
82
83
  use `~/.kintio` by default and accept `--home`. With multiple accounts, use `ilink list`
83
84
  and pass the displayed provider ID or account key through `--account`. Repeated `start`
84
85
  commands add accounts to the live runtime; `stop` removes one.
package/README.zh-CN.md CHANGED
@@ -54,7 +54,8 @@ kintio ilink start
54
54
  ```
55
55
 
56
56
  `ilink login` 完成一次扫码、加密保存凭据后退出,不会自行启动监听;`ilink start` 不启动 Hono 或 TCP 端口,
57
- 只以前台方式运行 iLink 长轮询和宿主 Agent。两者默认使用 `~/.kintio`。
57
+ 而是通过后台守护进程运行 iLink 长轮询和宿主 Agent。由外部进程管理器托管时可显式使用
58
+ `--foreground`。两者默认使用 `~/.kintio`。
58
59
  存在多个账号时,先用 `kintio ilink list` 查看账号,再通过 `--account` 指定
59
60
  `start`、`stop` 或 `delete` 的目标;正在运行时可继续执行 `start` 增加监听账号。
60
61
 
package/bin/kintio.js CHANGED
@@ -1,3 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- await import('../dist/cli.js');
3
+ const currentNode = process.versions.node;
4
+ const nodeMajor = Number.parseInt(currentNode.split('.')[0] || '', 10);
5
+ if (!Number.isSafeInteger(nodeMajor) || nodeMajor < 24) {
6
+ process.stderr.write(
7
+ `Kintio requires Node.js 24 or newer; current runtime is v${currentNode}.\n` +
8
+ 'Install Node.js 24+, then reinstall @kin-tio/cli in that Node environment.\n',
9
+ );
10
+ process.exitCode = 1;
11
+ } else {
12
+ await import('../dist/cli.js');
13
+ }
package/dist/daemon.js CHANGED
@@ -4,13 +4,18 @@ import { resolveProjectRoot } from './src/config.js';
4
4
  import { runNativeDaemon } from './src/runtime/native-daemon.js';
5
5
  const home = process.env.KINTIO_HOME;
6
6
  const configFile = process.env.KINTIO_CONFIG_FILE;
7
+ const mode = process.env.KINTIO_DAEMON_MODE || 'service';
7
8
  if (!home || !configFile) {
8
9
  throw new Error('KINTIO_HOME and KINTIO_CONFIG_FILE are required for daemon mode');
9
10
  }
11
+ if (mode !== 'service' && mode !== 'ilink') {
12
+ throw new Error(`Unsupported Kintio daemon mode: ${mode}`);
13
+ }
10
14
  try {
11
15
  await runNativeDaemon({
12
16
  home: path.resolve(home),
13
17
  configFile: path.resolve(configFile),
18
+ mode,
14
19
  packageRoot: resolveProjectRoot(import.meta.url),
15
20
  });
16
21
  }
package/dist/ilink.js ADDED
@@ -0,0 +1,53 @@
1
+ import { KINTIO_PACKAGE_ROOT, loadIlinkRuntimeConfig, } from './src/config.js';
2
+ import { startIlinkCliRuntime } from './src/ilink/cli-start.js';
3
+ import { installManagedSkill } from './src/runtime/managed-skill.js';
4
+ const config = loadIlinkRuntimeConfig();
5
+ installManagedSkill({
6
+ packageRoot: KINTIO_PACKAGE_ROOT,
7
+ workingDirectory: config.codex.workingDirectory,
8
+ });
9
+ const controller = new AbortController();
10
+ let resolveParentShutdown;
11
+ const parentShutdown = new Promise((resolve) => { resolveParentShutdown = resolve; });
12
+ const shutdown = () => {
13
+ controller.abort();
14
+ resolveParentShutdown();
15
+ };
16
+ process.once('SIGINT', shutdown);
17
+ process.once('SIGTERM', shutdown);
18
+ const handleMessage = (message) => {
19
+ if (message === 'shutdown')
20
+ shutdown();
21
+ };
22
+ process.on('message', handleMessage);
23
+ process.once('disconnect', shutdown);
24
+ if (process.env.KINTIO_MANAGED_WORKER === '1' && !process.connected)
25
+ shutdown();
26
+ try {
27
+ const result = await startIlinkCliRuntime({
28
+ background: true,
29
+ config,
30
+ signal: controller.signal,
31
+ stdout: (text) => process.stdout.write(text),
32
+ onStarted() {
33
+ process.send?.({ type: 'ready', pid: process.pid });
34
+ },
35
+ });
36
+ if (result === 0 && process.connected) {
37
+ process.send?.({ type: 'shutdown-request', pid: process.pid });
38
+ await parentShutdown;
39
+ }
40
+ process.exitCode = result === 130 ? 0 : result;
41
+ }
42
+ catch (error) {
43
+ console.error('[ilink] process failed', error);
44
+ process.exitCode = 1;
45
+ }
46
+ finally {
47
+ process.off('SIGINT', shutdown);
48
+ process.off('SIGTERM', shutdown);
49
+ process.off('disconnect', shutdown);
50
+ process.off('message', handleMessage);
51
+ if (process.connected)
52
+ process.disconnect();
53
+ }
package/dist/src/cli.js CHANGED
@@ -62,22 +62,22 @@ Options:
62
62
  `;
63
63
  const ILINK_START_HELP = `Usage: kintio ilink start [options]
64
64
 
65
- Run iLink long polling and the host Agent in the foreground without starting
65
+ Run iLink long polling and the host Agent in the background without starting
66
66
  Hono or opening a TCP listener. This command does not require setup or an
67
67
  environment file. One account is selected automatically; multiple accounts
68
- require --account. While this runtime is active, additional start commands add
69
- accounts to the same process.
68
+ require --account. Additional start commands add accounts to the same process.
70
69
 
71
70
  Options:
72
71
  --account <id> Provider account ID or Kintio account key
72
+ --foreground Keep the iLink-only Runtime attached to this terminal
73
73
  --home <directory> Instance directory (default: ~/.kintio)
74
74
  --config <file> Optional environment overrides
75
75
  -h, --help Show this help
76
76
  `;
77
77
  const ILINK_STOP_HELP = `Usage: kintio ilink stop [options]
78
78
 
79
- Stop one iLink account. Stopping the last account also exits a foreground
80
- "kintio ilink start" runtime. One account is selected automatically; multiple
79
+ Stop one iLink account. Stopping the last account also stops the background
80
+ iLink-only Runtime. One account is selected automatically; multiple
81
81
  accounts require --account.
82
82
 
83
83
  Options:
@@ -411,7 +411,7 @@ async function probeDaemon(location) {
411
411
  return undefined;
412
412
  }
413
413
  }
414
- function assertDaemonInstance(location, packageRoot) {
414
+ function assertDaemonInstance(location, packageRoot, mode) {
415
415
  const daemon = readDaemonRecord(location.home);
416
416
  if (!daemon)
417
417
  throw new Error('Kintio daemon record is missing');
@@ -419,6 +419,9 @@ function assertDaemonInstance(location, packageRoot) {
419
419
  !samePath(daemon.packageRoot, packageRoot)) {
420
420
  throw new Error('Kintio is running with another config or installation; use "kintio restart" to switch deliberately');
421
421
  }
422
+ if (mode && daemon.mode !== mode) {
423
+ throw new Error(`Kintio is already running in ${daemon.mode} mode; stop it before starting ${mode} mode`);
424
+ }
422
425
  }
423
426
  async function withLifecycleLock(location, task) {
424
427
  const dataDirectory = ensureContainedDirectory(location.home, path.join(location.home, 'data'));
@@ -474,18 +477,19 @@ async function rollbackLaunch(location, daemon) {
474
477
  }
475
478
  removeLaunchMetadata(location, daemon.pid);
476
479
  }
477
- async function start(location, runtime, restart) {
478
- const environment = processEnvironment(location, runtime);
480
+ async function startBackgroundDaemon(location, runtime, environment, mode, restart) {
479
481
  const timeout = parseStartTimeout(environment.KINTIO_START_TIMEOUT_MS);
480
482
  return withLifecycleLock(location, async () => {
481
483
  const existing = await probeDaemon(location);
482
484
  if (existing && !restart) {
483
- assertDaemonInstance(location, runtime.packageRoot);
485
+ assertDaemonInstance(location, runtime.packageRoot, mode);
484
486
  if (existing.phase !== 'running') {
485
487
  await waitUntilRunning(location, Date.now() + timeout);
486
488
  }
487
- runtime.stdout(`Kintio is already running (PID ${existing.workerPid || existing.daemonPid}).\n`);
488
- return 0;
489
+ return {
490
+ alreadyRunning: true,
491
+ pid: existing.workerPid || existing.daemonPid,
492
+ };
489
493
  }
490
494
  if (existing) {
491
495
  await stopDaemon(location, DAEMON_STOP_TIMEOUT_MS);
@@ -495,7 +499,7 @@ async function start(location, runtime, restart) {
495
499
  file: process.execPath,
496
500
  args: [path.join(runtime.packageRoot, 'dist/daemon.js')],
497
501
  cwd: location.home,
498
- env: environment,
502
+ env: { ...environment, KINTIO_DAEMON_MODE: mode },
499
503
  });
500
504
  try {
501
505
  await waitUntilRunning(location, deadline);
@@ -504,8 +508,39 @@ async function start(location, runtime, restart) {
504
508
  await rollbackLaunch(location, daemon);
505
509
  throw error;
506
510
  }
507
- return 0;
511
+ const running = await requestControl(location.home, 'ping');
512
+ return {
513
+ alreadyRunning: false,
514
+ pid: running.workerPid || running.daemonPid,
515
+ };
516
+ });
517
+ }
518
+ async function start(location, runtime, restart) {
519
+ const result = await startBackgroundDaemon(location, runtime, processEnvironment(location, runtime), 'service', restart);
520
+ if (result.alreadyRunning) {
521
+ runtime.stdout(`Kintio is already running (PID ${result.pid}).\n`);
522
+ }
523
+ return 0;
524
+ }
525
+ function ilinkDaemonEnvironment(location, runtime) {
526
+ const config = loadIlinkRuntimeConfig({
527
+ environment: { ...runtime.env },
528
+ envFile: location.configFile,
529
+ root: location.home,
508
530
  });
531
+ refreshManagedSkill(config.codex.workingDirectory, runtime);
532
+ return {
533
+ ...runtime.env,
534
+ KINTIO_HOME: location.home,
535
+ KINTIO_CONFIG_FILE: location.configFile,
536
+ NODE_ENV: 'production',
537
+ };
538
+ }
539
+ async function startIlinkDaemon(location, runtime) {
540
+ const result = await startBackgroundDaemon(location, runtime, ilinkDaemonEnvironment(location, runtime), 'ilink', false);
541
+ if (!result.alreadyRunning) {
542
+ runtime.stdout(`Kintio iLink runtime is running in background (PID ${result.pid}).\n`);
543
+ }
509
544
  }
510
545
  async function waitUntilRunning(location, deadline) {
511
546
  let lastError = 'daemon did not publish control state';
@@ -648,6 +683,7 @@ export async function runCli(args, overrides = {}) {
648
683
  'no-follow': { type: 'boolean' },
649
684
  'qr-output': { type: 'string' },
650
685
  account: { type: 'string' },
686
+ foreground: { type: 'boolean' },
651
687
  yes: { type: 'boolean' },
652
688
  help: { type: 'boolean', short: 'h' },
653
689
  version: { type: 'boolean', short: 'v' },
@@ -719,6 +755,9 @@ export async function runCli(args, overrides = {}) {
719
755
  if (parsed.values.yes && (command !== 'ilink' || subcommand !== 'delete')) {
720
756
  throw new Error('--yes is valid only for "kintio ilink delete"');
721
757
  }
758
+ if (parsed.values.foreground && (command !== 'ilink' || subcommand !== 'start')) {
759
+ throw new Error('--foreground is valid only for "kintio ilink start"');
760
+ }
722
761
  const location = instanceLocation(parsed.values, runtime);
723
762
  const qrOutputPath = parsed.values['qr-output'] === undefined
724
763
  ? undefined
@@ -748,6 +787,7 @@ export async function runCli(args, overrides = {}) {
748
787
  signal,
749
788
  });
750
789
  }
790
+ const foreground = Boolean(parsed.values.foreground);
751
791
  const commandResult = await runtime.ilinkAccount({
752
792
  command: subcommand,
753
793
  ...(parsed.values.account ? { selector: parsed.values.account } : {}),
@@ -756,18 +796,54 @@ export async function runCli(args, overrides = {}) {
756
796
  packageRoot: runtime.packageRoot,
757
797
  signal,
758
798
  stdout: runtime.stdout,
799
+ ...(subcommand === 'start'
800
+ ? { deferStandaloneStart: !foreground }
801
+ : {}),
759
802
  });
760
- if (subcommand !== 'start' || !commandResult.startForeground)
761
- return 0;
762
- return await runtime.ilinkStart({
763
- config: loadIlinkRuntimeConfig({
803
+ if (subcommand === 'start' && commandResult.runtimeRequired) {
804
+ const runtimeConfig = loadIlinkRuntimeConfig({
764
805
  environment: { ...runtime.env },
765
806
  envFile: location.configFile,
766
807
  root: location.home,
767
- }),
768
- signal,
769
- stdout: runtime.stdout,
770
- });
808
+ });
809
+ if (foreground) {
810
+ refreshManagedSkill(runtimeConfig.codex.workingDirectory, runtime);
811
+ return await runtime.ilinkStart({
812
+ config: runtimeConfig,
813
+ signal,
814
+ stdout: runtime.stdout,
815
+ });
816
+ }
817
+ if (!commandResult.selectedAccountKey) {
818
+ throw new Error('iLink start did not resolve an account identity');
819
+ }
820
+ await startIlinkDaemon(location, runtime);
821
+ await runtime.ilinkAccount({
822
+ command: 'start',
823
+ selector: commandResult.selectedAccountKey,
824
+ config: enrollmentConfig,
825
+ packageRoot: runtime.packageRoot,
826
+ signal,
827
+ stdout: runtime.stdout,
828
+ });
829
+ return 0;
830
+ }
831
+ if ((subcommand === 'stop' || subcommand === 'delete') &&
832
+ commandResult.runningCount === 0 &&
833
+ readDaemonRecord(location.home)?.mode === 'ilink') {
834
+ await withLifecycleLock(location, async () => {
835
+ if (readDaemonRecord(location.home)?.mode === 'ilink') {
836
+ try {
837
+ await stopDaemon(location, DAEMON_STOP_TIMEOUT_MS);
838
+ }
839
+ catch (error) {
840
+ if (readDaemonRecord(location.home))
841
+ throw error;
842
+ }
843
+ }
844
+ });
845
+ }
846
+ return 0;
771
847
  });
772
848
  }
773
849
  if (command === 'setup')
@@ -793,7 +869,7 @@ export async function runCli(args, overrides = {}) {
793
869
  return 0;
794
870
  }
795
871
  assertDaemonInstance(location, runtime.packageRoot);
796
- runtime.stdout(`Kintio is ${existing.phase} ` +
872
+ runtime.stdout(`Kintio is ${existing.phase} in ${readDaemonRecord(location.home)?.mode || 'service'} mode ` +
797
873
  `(daemon PID ${existing.daemonPid}` +
798
874
  `${existing.workerPid ? `, worker PID ${existing.workerPid}` : ''}).` +
799
875
  `${existing.message ? ` ${existing.message}` : ''}\n`);
@@ -22,11 +22,11 @@ function selectAccount(accounts, selector, runtimeActive) {
22
22
  }
23
23
  return matches[0];
24
24
  }
25
- export async function runIlinkAccountCommand({ command, selector, confirmed = false, config, packageRoot, signal, stdout, openControl, }) {
25
+ export async function runIlinkAccountCommand({ command, selector, confirmed = false, config, packageRoot, signal, stdout, openControl, deferStandaloneStart = false, }) {
26
26
  if (!openControl && !fs.existsSync(config.state.databaseFile)) {
27
27
  if (command === 'list') {
28
28
  stdout('No iLink accounts enrolled.\n');
29
- return { startForeground: false, runningCount: 0 };
29
+ return { runtimeRequired: false, runningCount: 0 };
30
30
  }
31
31
  throw new Error('No iLink account is enrolled; run "kintio ilink login" first');
32
32
  }
@@ -37,14 +37,21 @@ export async function runIlinkAccountCommand({ command, selector, confirmed = fa
37
37
  const runtimeActive = control.mode === 'runtime';
38
38
  if (command === 'list') {
39
39
  stdout(accounts.length
40
- ? `${choices(accounts, runtimeActive)}\n`
40
+ ? `${accounts.map((account) => account.providerAccountId).join('\n')}\n`
41
41
  : 'No iLink accounts enrolled.\n');
42
42
  return {
43
- startForeground: false,
43
+ runtimeRequired: false,
44
44
  runningCount: accounts.filter((account) => account.runtimeEnabled).length,
45
45
  };
46
46
  }
47
47
  const account = selectAccount(accounts, selector, runtimeActive);
48
+ if (command === 'start' && control.mode === 'standalone' && deferStandaloneStart) {
49
+ return {
50
+ runtimeRequired: true,
51
+ runningCount: 0,
52
+ selectedAccountKey: account.accountKey,
53
+ };
54
+ }
48
55
  if (command === 'delete' && !confirmed) {
49
56
  throw new Error(`Deleting ${JSON.stringify(account.providerAccountId)} permanently removes the account, ` +
50
57
  'credentials, conversations, messages, media, send records, and audit records; ' +
@@ -56,8 +63,9 @@ export async function runIlinkAccountCommand({ command, selector, confirmed = fa
56
63
  stdout(`${command === 'delete' ? 'Deleted' : command === 'start' ? 'Started' : 'Stopped'} ` +
57
64
  `${JSON.stringify(account.providerAccountId)}.\n`);
58
65
  return {
59
- startForeground: command === 'start' && control.mode === 'standalone',
66
+ runtimeRequired: command === 'start' && control.mode === 'standalone',
60
67
  runningCount: result.runningCount,
68
+ ...(command === 'start' ? { selectedAccountKey: account.accountKey } : {}),
61
69
  };
62
70
  }
63
71
  finally {
@@ -44,7 +44,10 @@ export async function startIlinkCliRuntime(options) {
44
44
  });
45
45
  try {
46
46
  await runtime.start();
47
- options.stdout('Kintio iLink runtime is active. Press Ctrl-C to stop.\n');
47
+ await options.onStarted?.();
48
+ options.stdout(options.background
49
+ ? 'Kintio iLink runtime is active.\n'
50
+ : 'Kintio iLink runtime is active. Press Ctrl-C to stop.\n');
48
51
  const reason = await Promise.race([
49
52
  waitForAbort(options.signal).then(() => 'signal'),
50
53
  stopRequested.then(() => 'account-stop'),
@@ -19,6 +19,7 @@ const daemonRecordSchema = z.strictObject({
19
19
  runId,
20
20
  daemonPid: positiveInteger,
21
21
  configFile: absolutePath,
22
+ mode: z.enum(['service', 'ilink']).default('service'),
22
23
  packageRoot: absolutePath,
23
24
  token,
24
25
  });
@@ -56,7 +56,7 @@ function forceKill(worker) {
56
56
  if (worker.exitCode === null && worker.signalCode === null)
57
57
  worker.kill('SIGKILL');
58
58
  }
59
- export async function runNativeDaemon({ home, configFile, packageRoot, environment = process.env, }) {
59
+ export async function runNativeDaemon({ home, configFile, packageRoot, mode = 'service', environment = process.env, }) {
60
60
  const instanceHome = path.resolve(home);
61
61
  const instanceConfig = path.resolve(configFile);
62
62
  const dataDirectory = ensurePrivateDirectory(path.join(instanceHome, 'data'));
@@ -140,7 +140,7 @@ export async function runNativeDaemon({ home, configFile, packageRoot, environme
140
140
  if (phase === 'stopping' || phase === 'failed')
141
141
  return;
142
142
  phase = 'starting';
143
- const child = spawn(process.execPath, [path.join(instancePackageRoot, 'dist/index.js')], {
143
+ const child = spawn(process.execPath, [path.join(instancePackageRoot, mode === 'ilink' ? 'dist/ilink.js' : 'dist/index.js')], {
144
144
  cwd: instanceHome,
145
145
  env: {
146
146
  ...environment,
@@ -181,7 +181,7 @@ export async function runNativeDaemon({ home, configFile, packageRoot, environme
181
181
  readyTimer.unref?.();
182
182
  child.stdout?.on('data', (chunk) => log.write(chunk));
183
183
  child.stderr?.on('data', (chunk) => log.write(chunk));
184
- child.once('message', (message) => {
184
+ child.on('message', (message) => {
185
185
  if (worker === child &&
186
186
  !readinessExpired &&
187
187
  phase !== 'stopping' &&
@@ -195,6 +195,13 @@ export async function runNativeDaemon({ home, configFile, packageRoot, environme
195
195
  phase = 'running';
196
196
  lastError = undefined;
197
197
  }
198
+ if (worker === child &&
199
+ phase === 'running' &&
200
+ message && typeof message === 'object' &&
201
+ 'type' in message && message.type === 'shutdown-request' &&
202
+ 'pid' in message && message.pid === child.pid) {
203
+ void shutdown();
204
+ }
198
205
  });
199
206
  child.once('error', (error) => log.line(`worker spawn error: ${error.message}`));
200
207
  workerExit = new Promise((resolve) => {
@@ -307,6 +314,7 @@ export async function runNativeDaemon({ home, configFile, packageRoot, environme
307
314
  runId,
308
315
  daemonPid: process.pid,
309
316
  configFile: instanceConfig,
317
+ mode,
310
318
  packageRoot: instancePackageRoot,
311
319
  token,
312
320
  });
@@ -192,8 +192,9 @@ export async function createRuntime({ config, logger = console, onIlinkStopReque
192
192
  await ilinkListener?.refresh();
193
193
  const runningCount = enrollment.accounts
194
194
  .listRuntimeAccountsWithSecrets().length;
195
- if (!enabled && runningCount === 0)
196
- onIlinkStopRequested?.();
195
+ if (!enabled && runningCount === 0 && onIlinkStopRequested) {
196
+ setImmediate(onIlinkStopRequested);
197
+ }
197
198
  return {
198
199
  account: {
199
200
  accountKey: account.accountKey,
@@ -211,8 +212,9 @@ export async function createRuntime({ config, logger = console, onIlinkStopReque
211
212
  await ilinkListener?.refresh();
212
213
  const runningCount = enrollment.accounts
213
214
  .listRuntimeAccountsWithSecrets().length;
214
- if (runningCount === 0)
215
- onIlinkStopRequested?.();
215
+ if (runningCount === 0 && onIlinkStopRequested) {
216
+ setImmediate(onIlinkStopRequested);
217
+ }
216
218
  return {
217
219
  account: {
218
220
  accountKey: account.accountKey,
@@ -207,10 +207,19 @@ export class CodexAppServer {
207
207
  this.#pending.delete(message.id);
208
208
  const rpcError = asRecord(message.error);
209
209
  if (rpcError) {
210
- const error = new Error(`Codex app-server request failed: ${pending.method}`);
211
- if (typeof rpcError.code === 'number' && Number.isSafeInteger(rpcError.code)) {
212
- error.code = rpcError.code;
213
- }
210
+ const code = typeof rpcError.code === 'number' && Number.isSafeInteger(rpcError.code)
211
+ ? rpcError.code
212
+ : undefined;
213
+ const errorData = asRecord(rpcError.data);
214
+ const category = codexFailureLabel(errorData?.codexErrorInfo ?? rpcError.codexErrorInfo);
215
+ const diagnostic = [
216
+ ...(code === undefined ? [] : [`code ${code}`]),
217
+ ...(category ? [`category ${category}`] : []),
218
+ ];
219
+ const error = new Error(`Codex app-server request failed: ${pending.method}` +
220
+ (diagnostic.length ? ` (${diagnostic.join('; ')})` : ''));
221
+ if (code !== undefined)
222
+ error.code = code;
214
223
  pending.reject(error);
215
224
  }
216
225
  else {
@@ -1 +1 @@
1
- export const KINTIO_VERSION = '0.7.0';
1
+ export const KINTIO_VERSION = '0.7.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kin-tio/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "license": "Apache-2.0",
5
5
  "author": "XIE YU",
6
6
  "packageManager": "pnpm@10.34.5",