@kin-tio/cli 0.6.0
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/.env.example +46 -0
- package/CHANGELOG.md +95 -0
- package/LICENSE +202 -0
- package/README.md +150 -0
- package/README.zh-CN.md +79 -0
- package/THIRD_PARTY_NOTICES +31 -0
- package/assets/ilink-login-card.png +0 -0
- package/bin/kintio.js +3 -0
- package/codex-workspace/.agents/skills/wechat-kf-reply-sop/SKILL.md +58 -0
- package/dist/cli.js +3 -0
- package/dist/daemon.js +28 -0
- package/dist/index.js +70 -0
- package/dist/mcp-relay.js +11 -0
- package/dist/src/agent/runtime.js +1 -0
- package/dist/src/app.js +34 -0
- package/dist/src/cli.js +578 -0
- package/dist/src/config.js +237 -0
- package/dist/src/domain/message.js +23 -0
- package/dist/src/domain/send-contract.js +205 -0
- package/dist/src/domain/wecom-message.js +281 -0
- package/dist/src/ilink/executor.js +306 -0
- package/dist/src/ilink/inbound-image.js +310 -0
- package/dist/src/ilink/listener.js +306 -0
- package/dist/src/ilink/login-manager.js +198 -0
- package/dist/src/ilink/login-store.js +197 -0
- package/dist/src/ilink/media-gateway.js +83 -0
- package/dist/src/ilink/media.js +267 -0
- package/dist/src/ilink/message.js +247 -0
- package/dist/src/ilink/protocol/client.js +464 -0
- package/dist/src/ilink/protocol/types.js +35 -0
- package/dist/src/ilink/qr.js +109 -0
- package/dist/src/ilink/secret-box.js +143 -0
- package/dist/src/ilink/sqlite-store.js +1194 -0
- package/dist/src/ilink/store-types.js +63 -0
- package/dist/src/lib/image-format.js +23 -0
- package/dist/src/lib/path-identity.js +38 -0
- package/dist/src/lib/private-directory.js +51 -0
- package/dist/src/lib/text.js +19 -0
- package/dist/src/lib/wecom-crypto.js +74 -0
- package/dist/src/lib/xml.js +8 -0
- package/dist/src/mcp/conversation-memory-server.js +179 -0
- package/dist/src/mcp/ilink-server.js +158 -0
- package/dist/src/mcp/ipc-host.js +275 -0
- package/dist/src/mcp/ipc-protocol.js +226 -0
- package/dist/src/mcp/stdio-relay.js +122 -0
- package/dist/src/mcp/wechat-kf-executor.js +295 -0
- package/dist/src/mcp/wechat-kf-server.js +208 -0
- package/dist/src/routes/wecom.js +89 -0
- package/dist/src/runtime/daemon-protocol.js +202 -0
- package/dist/src/runtime/managed-skill.js +49 -0
- package/dist/src/runtime/native-daemon.js +325 -0
- package/dist/src/runtime/single-instance-lock.js +167 -0
- package/dist/src/runtime.js +503 -0
- package/dist/src/services/codex-agent.js +542 -0
- package/dist/src/services/codex-app-server.js +436 -0
- package/dist/src/services/conversation-processor.js +762 -0
- package/dist/src/services/image-stager.js +49 -0
- package/dist/src/services/media-gateway.js +83 -0
- package/dist/src/services/wecom-api.js +311 -0
- package/dist/src/services/wecom-sync.js +316 -0
- package/dist/src/state/persistence.js +124 -0
- package/dist/src/state/sqlite-store.js +3102 -0
- package/dist/src/supervisor.js +212 -0
- package/dist/src/types.js +1 -0
- package/dist/src/version.js +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createAdaptorServer } from '@hono/node-server';
|
|
2
|
+
import { createApp } from './app.js';
|
|
3
|
+
import { createRuntime } from './runtime.js';
|
|
4
|
+
export class KintioSupervisor {
|
|
5
|
+
#config;
|
|
6
|
+
#logger;
|
|
7
|
+
#runtimeFactory;
|
|
8
|
+
#onFatal;
|
|
9
|
+
#runtime;
|
|
10
|
+
#runtimeIngressStopped = false;
|
|
11
|
+
#runtimeAbort;
|
|
12
|
+
#runtimeClose;
|
|
13
|
+
#server;
|
|
14
|
+
#httpClosing;
|
|
15
|
+
#state = 'idle';
|
|
16
|
+
#ingressOpen = false;
|
|
17
|
+
#starting;
|
|
18
|
+
#closing;
|
|
19
|
+
#aborting;
|
|
20
|
+
constructor({ config, logger = console, runtime, onFatal = (error) => logger.error(`[supervisor] fatal channel error: ${error.message}`), }) {
|
|
21
|
+
this.#config = config;
|
|
22
|
+
this.#logger = logger;
|
|
23
|
+
this.#runtimeFactory = runtime
|
|
24
|
+
? async () => runtime
|
|
25
|
+
: () => createRuntime({ config, logger });
|
|
26
|
+
this.#onFatal = onFatal;
|
|
27
|
+
}
|
|
28
|
+
get state() {
|
|
29
|
+
return this.#state;
|
|
30
|
+
}
|
|
31
|
+
start() {
|
|
32
|
+
if (this.#starting &&
|
|
33
|
+
(this.#state === 'starting' || this.#state === 'running'))
|
|
34
|
+
return this.#starting;
|
|
35
|
+
if (this.#state !== 'idle') {
|
|
36
|
+
return Promise.reject(new Error(`Kintio supervisor cannot start from ${this.#state}`));
|
|
37
|
+
}
|
|
38
|
+
this.#state = 'starting';
|
|
39
|
+
this.#starting = (async () => {
|
|
40
|
+
let startupErrorListener;
|
|
41
|
+
try {
|
|
42
|
+
const runtime = await this.#runtimeFactory();
|
|
43
|
+
this.#runtime = runtime;
|
|
44
|
+
this.#assertStarting();
|
|
45
|
+
const app = createApp({
|
|
46
|
+
config: this.#config,
|
|
47
|
+
logger: this.#logger,
|
|
48
|
+
messageProcessor: runtime.messageProcessor,
|
|
49
|
+
acceptIngress: () => this.#ingressOpen,
|
|
50
|
+
});
|
|
51
|
+
const server = createAdaptorServer({ fetch: app.fetch });
|
|
52
|
+
this.#server = server;
|
|
53
|
+
let rejectChannelFailure;
|
|
54
|
+
const channelFailure = new Promise((_resolve, reject) => {
|
|
55
|
+
rejectChannelFailure = reject;
|
|
56
|
+
});
|
|
57
|
+
startupErrorListener = (error) => rejectChannelFailure(error);
|
|
58
|
+
server.on('error', startupErrorListener);
|
|
59
|
+
const port = await Promise.race([
|
|
60
|
+
this.#listen(server),
|
|
61
|
+
channelFailure,
|
|
62
|
+
]);
|
|
63
|
+
this.#assertStarting();
|
|
64
|
+
await Promise.race([runtime.start(), channelFailure]);
|
|
65
|
+
this.#assertStarting();
|
|
66
|
+
this.#ingressOpen = true;
|
|
67
|
+
this.#state = 'running';
|
|
68
|
+
server.on('error', (error) => this.#fatal(error));
|
|
69
|
+
server.off('error', startupErrorListener);
|
|
70
|
+
startupErrorListener = undefined;
|
|
71
|
+
this.#logger.info(`Hono server is listening on port ${port}`);
|
|
72
|
+
return Object.freeze({ port });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
this.#ingressOpen = false;
|
|
76
|
+
if (this.#state === 'starting')
|
|
77
|
+
this.#state = 'failed';
|
|
78
|
+
try {
|
|
79
|
+
await this.#cleanupFailedStart();
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (startupErrorListener && this.#server) {
|
|
83
|
+
this.#server.off('error', startupErrorListener);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
return this.#starting;
|
|
90
|
+
}
|
|
91
|
+
close() {
|
|
92
|
+
if (this.#closing)
|
|
93
|
+
return this.#closing;
|
|
94
|
+
if (this.#state === 'closed')
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
if (this.#state === 'aborted') {
|
|
97
|
+
return Promise.reject(new Error('Process-aborted supervisor requires process exit'));
|
|
98
|
+
}
|
|
99
|
+
this.#state = 'closing';
|
|
100
|
+
this.#ingressOpen = false;
|
|
101
|
+
this.#stopRuntimeIngress();
|
|
102
|
+
this.#closing = (async () => {
|
|
103
|
+
await this.#starting?.catch(() => undefined);
|
|
104
|
+
try {
|
|
105
|
+
await this.#closeRuntime();
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await this.#closeHttp(false).catch(() => undefined);
|
|
109
|
+
if (!this.#aborting)
|
|
110
|
+
this.#state = 'closed';
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
return this.#closing;
|
|
114
|
+
}
|
|
115
|
+
abortForExit() {
|
|
116
|
+
if (this.#aborting)
|
|
117
|
+
return this.#aborting;
|
|
118
|
+
if (this.#state === 'closed')
|
|
119
|
+
return Promise.resolve();
|
|
120
|
+
this.#state = 'closing';
|
|
121
|
+
this.#ingressOpen = false;
|
|
122
|
+
this.#stopRuntimeIngress();
|
|
123
|
+
this.#aborting = (async () => {
|
|
124
|
+
try {
|
|
125
|
+
await Promise.all([
|
|
126
|
+
this.#abortRuntime(),
|
|
127
|
+
this.#closeHttp(true).catch(() => undefined),
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
this.#state = 'aborted';
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
return this.#aborting;
|
|
135
|
+
}
|
|
136
|
+
#assertStarting() {
|
|
137
|
+
if (this.#state !== 'starting') {
|
|
138
|
+
throw new Error('Kintio supervisor startup was cancelled');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
#listen(server) {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
server.listen(this.#config.port, '0.0.0.0', () => {
|
|
144
|
+
const address = server.address();
|
|
145
|
+
if (!address || typeof address === 'string') {
|
|
146
|
+
reject(new Error('Hono listener has no TCP address'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
resolve(address.port);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
#fatal(error) {
|
|
154
|
+
if (this.#state !== 'running')
|
|
155
|
+
return;
|
|
156
|
+
this.#state = 'failed';
|
|
157
|
+
this.#ingressOpen = false;
|
|
158
|
+
void this.abortForExit().catch((abortError) => {
|
|
159
|
+
this.#logger.error(`[supervisor] fatal cleanup failed: ${abortError instanceof Error ? abortError.message : String(abortError)}`);
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
this.#onFatal(error);
|
|
163
|
+
}
|
|
164
|
+
catch (hookError) {
|
|
165
|
+
this.#logger.error(`[supervisor] fatal hook failed: ${hookError instanceof Error ? hookError.message : String(hookError)}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async #cleanupFailedStart() {
|
|
169
|
+
this.#stopRuntimeIngress();
|
|
170
|
+
try {
|
|
171
|
+
await this.#abortRuntime();
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await this.#closeRuntime().catch(() => undefined);
|
|
175
|
+
await this.#closeHttp(true).catch(() => undefined);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
#stopRuntimeIngress() {
|
|
179
|
+
if (this.#runtimeIngressStopped)
|
|
180
|
+
return;
|
|
181
|
+
if (!this.#runtime)
|
|
182
|
+
return;
|
|
183
|
+
this.#runtimeIngressStopped = true;
|
|
184
|
+
this.#runtime.stopAccepting();
|
|
185
|
+
}
|
|
186
|
+
#abortRuntime() {
|
|
187
|
+
const runtime = this.#runtime;
|
|
188
|
+
if (!runtime)
|
|
189
|
+
return Promise.resolve();
|
|
190
|
+
this.#runtimeAbort ||= runtime.abort();
|
|
191
|
+
return this.#runtimeAbort;
|
|
192
|
+
}
|
|
193
|
+
#closeRuntime() {
|
|
194
|
+
this.#runtimeClose ||= this.#runtime?.close() || Promise.resolve();
|
|
195
|
+
return this.#runtimeClose;
|
|
196
|
+
}
|
|
197
|
+
async #closeHttp(force) {
|
|
198
|
+
const server = this.#server;
|
|
199
|
+
if (!server)
|
|
200
|
+
return;
|
|
201
|
+
if (!this.#httpClosing && server.listening) {
|
|
202
|
+
this.#httpClosing = new Promise((resolve, reject) => {
|
|
203
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (force) {
|
|
207
|
+
server
|
|
208
|
+
.closeAllConnections?.();
|
|
209
|
+
}
|
|
210
|
+
await this.#httpClosing;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const KINTIO_VERSION = '0.6.0';
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kin-tio/cli",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"author": "XIE YU",
|
|
6
|
+
"packageManager": "pnpm@10.34.5",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
12
|
+
"description": "Connect chat channels to an Agent you control",
|
|
13
|
+
"bin": {
|
|
14
|
+
"kintio": "bin/kintio.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/kintio.js",
|
|
18
|
+
"dist",
|
|
19
|
+
"assets/ilink-login-card.png",
|
|
20
|
+
"codex-workspace/.agents/skills/wechat-kf-reply-sop/SKILL.md",
|
|
21
|
+
".env.example",
|
|
22
|
+
"README.md",
|
|
23
|
+
"README.zh-CN.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"THIRD_PARTY_NOTICES"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/Gkxie/kintio.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/Gkxie/kintio/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/Gkxie/kintio#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"chat",
|
|
38
|
+
"im",
|
|
39
|
+
"ai-agent",
|
|
40
|
+
"codex",
|
|
41
|
+
"mcp"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
|
|
45
|
+
"dev": "node --watch index.ts",
|
|
46
|
+
"start": "pnpm run build && node dist/index.js",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"prepack": "pnpm run build"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=24"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@hono/node-server": "^2.0.12",
|
|
55
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
56
|
+
"cross-spawn": "7.0.6",
|
|
57
|
+
"hono": "^4.13.5",
|
|
58
|
+
"pngjs": "5.0.0",
|
|
59
|
+
"qrcode": "1.5.4",
|
|
60
|
+
"zod": "^4.4.3"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/cross-spawn": "6.0.6",
|
|
64
|
+
"@types/node": "24.13.3",
|
|
65
|
+
"@types/pngjs": "^6.0.5",
|
|
66
|
+
"@types/qrcode": "1.5.6",
|
|
67
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
68
|
+
"knip": "6.32.2",
|
|
69
|
+
"typescript": "^7.0.2",
|
|
70
|
+
"vitest": "4.1.11"
|
|
71
|
+
}
|
|
72
|
+
}
|