@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 +8 -0
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/bin/kintio.js +11 -1
- package/dist/daemon.js +5 -0
- package/dist/ilink.js +53 -0
- package/dist/src/cli.js +98 -22
- package/dist/src/ilink/cli-accounts.js +13 -5
- package/dist/src/ilink/cli-start.js +4 -1
- package/dist/src/runtime/daemon-protocol.js +1 -0
- package/dist/src/runtime/native-daemon.js +11 -3
- package/dist/src/runtime.js +6 -4
- package/dist/src/services/codex-app-server.js +13 -4
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
80
|
-
|
|
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
|
|
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
|
-
|
|
488
|
-
|
|
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
|
-
|
|
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
|
|
761
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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 {
|
|
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
|
-
? `${
|
|
40
|
+
? `${accounts.map((account) => account.providerAccountId).join('\n')}\n`
|
|
41
41
|
: 'No iLink accounts enrolled.\n');
|
|
42
42
|
return {
|
|
43
|
-
|
|
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
|
-
|
|
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.
|
|
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'),
|
|
@@ -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.
|
|
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
|
});
|
package/dist/src/runtime.js
CHANGED
|
@@ -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
|
|
211
|
-
|
|
212
|
-
|
|
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 {
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const KINTIO_VERSION = '0.7.
|
|
1
|
+
export const KINTIO_VERSION = '0.7.1';
|