@logicseek/hello-cc 1.0.1 → 1.0.2

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +4 -0
  3. package/README.zh-CN.md +4 -0
  4. package/docs/README.md +9 -0
  5. package/docs/README.zh-CN.md +9 -0
  6. package/docs/commands.md +53 -0
  7. package/docs/commands.zh-CN.md +48 -0
  8. package/docs/dsh.md +137 -0
  9. package/docs/dsh.zh-CN.md +139 -0
  10. package/docs/mcp.md +9 -0
  11. package/docs/mcp.zh-CN.md +23 -0
  12. package/docs/native.md +295 -0
  13. package/docs/native.zh-CN.md +253 -0
  14. package/docs/web-handoff.zh-CN.md +53 -0
  15. package/lib/cli/app.mjs +25 -6
  16. package/lib/cli/commands/doctor.mjs +17 -2
  17. package/lib/cli/commands/dsh.mjs +79 -0
  18. package/lib/cli/commands/hook.mjs +241 -90
  19. package/lib/cli/commands/lock.mjs +34 -19
  20. package/lib/cli/commands/mcp.mjs +28 -0
  21. package/lib/cli/commands/native.mjs +130 -0
  22. package/lib/cli/commands/run.mjs +66 -21
  23. package/lib/cli/commands/task.mjs +6 -3
  24. package/lib/cli-args.mjs +4 -0
  25. package/lib/coordination-state.mjs +15 -1
  26. package/lib/core/coordination/event-retention.mjs +14 -2
  27. package/lib/core/coordination/gc-plan.mjs +3 -13
  28. package/lib/core/coordination/locks.mjs +45 -0
  29. package/lib/core/coordination/results.mjs +79 -0
  30. package/lib/core/peers/bindings.mjs +9 -3
  31. package/lib/diagnostics/codex.mjs +121 -0
  32. package/lib/integrations/codex-interactions.mjs +114 -0
  33. package/lib/integrations/dsh-collaboration.mjs +170 -0
  34. package/lib/integrations/dsh-cordis.d.ts +19 -0
  35. package/lib/integrations/dsh-cordis.mjs +177 -0
  36. package/lib/integrations/dsh.bundle.yml +7 -0
  37. package/lib/integrations/dsh.mjs +287 -0
  38. package/lib/integrations/native/claude.mjs +451 -0
  39. package/lib/integrations/native/codex.mjs +242 -0
  40. package/lib/integrations/native/dsh-acp.mjs +343 -0
  41. package/lib/integrations/native/index.mjs +19 -0
  42. package/lib/integrations/native/interactions.mjs +53 -0
  43. package/lib/integrations/native/jsonrpc.mjs +185 -0
  44. package/lib/integrations/peers/identity.mjs +36 -10
  45. package/lib/integrations/peers/processes.mjs +22 -8
  46. package/lib/integrations/providers.mjs +6 -2
  47. package/lib/mcp/scope.mjs +104 -0
  48. package/lib/mcp/stdio.mjs +80 -0
  49. package/lib/mcp/tools.mjs +197 -0
  50. package/lib/process/identity.mjs +3 -1
  51. package/lib/release/release-notes.mjs +9 -0
  52. package/lib/runtime/native/client.mjs +38 -0
  53. package/lib/runtime/native/service.mjs +511 -0
  54. package/lib/runtime/native/store.mjs +121 -0
  55. package/lib/ui/help.mjs +5 -2
  56. package/lib/web/adoption-state.mjs +55 -0
  57. package/lib/web/auto-attach.mjs +18 -2
  58. package/lib/web/codex-app-server.mjs +557 -0
  59. package/lib/web/codex-sessions.mjs +287 -0
  60. package/lib/web/control-lease.mjs +204 -0
  61. package/lib/web/http-routes.mjs +197 -15
  62. package/lib/web/http.mjs +5 -4
  63. package/lib/web/native-sessions.mjs +286 -0
  64. package/lib/web/project-contexts.mjs +2 -1
  65. package/lib/web/pty-sessions.mjs +60 -33
  66. package/lib/web/runtime-main.mjs +98 -6
  67. package/lib/web/session-serialize.mjs +0 -0
  68. package/lib/web/task-results.mjs +76 -0
  69. package/lib/web/tmux-sessions.mjs +92 -11
  70. package/lib/web/tmux-stream.mjs +2 -2
  71. package/lib/web/ui-codex.mjs +550 -0
  72. package/lib/web/ui-command-palette.mjs +119 -0
  73. package/lib/web/ui-handoff.mjs +102 -0
  74. package/lib/web/ui-history.mjs +118 -0
  75. package/lib/web/ui-i18n.mjs +841 -0
  76. package/lib/web/ui-interactions.mjs +78 -0
  77. package/lib/web/ui-login.mjs +101 -0
  78. package/lib/web/ui-native.mjs +165 -0
  79. package/lib/web/ui-preferences.mjs +278 -0
  80. package/lib/web/ui-review.mjs +132 -0
  81. package/lib/web/ui-template.mjs +1106 -576
  82. package/lib/web/ui-terminal-find.mjs +98 -0
  83. package/lib/web/ui-workbench.mjs +94 -0
  84. package/lib/web/ui-workspace.mjs +222 -0
  85. package/package.json +11 -3
  86. package/scripts/check-free-identifiers.mjs +67 -8
  87. package/scripts/codex-diagnostics-installed-acceptance.mjs +72 -0
  88. package/scripts/dsh-cordis-acceptance.mjs +200 -0
  89. package/scripts/dsh-installed-acceptance.mjs +247 -0
  90. package/scripts/dsh-official-acceptance.mjs +180 -0
  91. package/scripts/dsh-web-startup-acceptance.mjs +87 -0
  92. package/scripts/github-release.mjs +3 -1
  93. package/scripts/native-live-acceptance.mjs +369 -0
  94. package/scripts/regression.mjs +243 -89
  95. package/scripts/web-handoff-installed-acceptance.mjs +98 -0
  96. package/scripts/web-native-interaction-acceptance.mjs +204 -0
  97. package/scripts/web-native-model-acceptance.mjs +138 -0
@@ -0,0 +1,197 @@
1
+ import { CliError } from '../shared/errors.mjs';
2
+ import { redactSecrets } from '../shared/redact.mjs';
3
+ import { parseOpts, intOpt, required, positiveSafeIntOpt, wantsHelp } from '../cli-args.mjs';
4
+ import { positiveIntOpt } from '../task-cli.mjs';
5
+ import { tx } from '../db/schema.mjs';
6
+ import { createMsgCommands } from '../cli/commands/msg.mjs';
7
+ import { createTaskCommands } from '../cli/commands/task.mjs';
8
+ import { createLockCommands } from '../cli/commands/lock.mjs';
9
+ import { createCoordinationCommands } from '../cli/commands/coordination.mjs';
10
+ import { createCoordinationState } from '../coordination-state.mjs';
11
+ import { createMessageStore } from '../core/coordination/messages.mjs';
12
+ import { createTaskStore } from '../core/coordination/tasks.mjs';
13
+ import { createEvidenceRuntime } from '../core/peers/evidence-runtime.mjs';
14
+ import { annotateTasksWithLiveness } from '../core/peers/liveness.mjs';
15
+ import { clockGraceSuppressed, readClockGraceUntil } from '../shared/clock-grace.mjs';
16
+ import { clockSafetyUnavailable } from '../core/coordination/clock-safety.mjs';
17
+ import { leaseDeadline } from '../core/coordination/lease-renewal.mjs';
18
+ import { scopedLockResource, lockLabel, lockScope, locksConflict } from '../core/coordination/locks.mjs';
19
+ import { captureLockAcquireSubject, sameLockAcquireSubject, observeLockOwnerEvidence } from '../core/coordination/lock-evidence.mjs';
20
+ import { runOptimisticEvidenceMutation } from '../core/coordination/optimistic-evidence.mjs';
21
+ import { normalizeListText } from '../handoff.mjs';
22
+ import { listTaskResults, recordTaskResult } from '../core/coordination/results.mjs';
23
+
24
+ const string = (maxLength = 4096) => ({ type: 'string', minLength: 1, maxLength });
25
+ const integer = (maximum = Number.MAX_SAFE_INTEGER) => ({ type: 'integer', minimum: 1, maximum });
26
+ const strings = { type: 'array', maxItems: 50, items: string() };
27
+ const definitions = [
28
+ ['hcc_state', 'Read coordination state for this project and peer.', {}, [], true],
29
+ ['hcc_task_list', 'List tasks visible to this peer, with owner evidence.',
30
+ { limit: integer(100), all: { type: 'boolean' } }, [], true],
31
+ ['hcc_inbox', 'Read this peer inbox without acknowledging messages.',
32
+ { limit: integer(100), all: { type: 'boolean' } }, [], true],
33
+ ['hcc_message_send', 'Send a coordination message as this executor peer.',
34
+ { to: string(256), body: string(16000), task_id: integer(), kind: { type: 'string', enum: ['note', 'ask', 'progress'] } }, ['to', 'body'], false],
35
+ ['hcc_task_next', 'Return the current task or claim one pending task. Never completes a task.', {}, [], false],
36
+ ['hcc_handoff', 'Record work, tests and risks for an owned task without completing it.',
37
+ { task_id: integer(), summary: string(16000), to: string(256), changed_files: strings, tests: strings, risks: strings }, ['task_id', 'summary'], false],
38
+ ['hcc_lock_acquire', 'Acquire a scoped resource lock for this peer and its task.',
39
+ { resource: string(1024), task_id: integer(), scope: string(256), ttl: integer(86400), reason: string(4096) }, ['resource', 'task_id'], false],
40
+ ['hcc_lock_release', 'Release this peer own lock; cannot force another owner lock.',
41
+ { resource: string(1024), scope: string(256) }, ['resource'], false],
42
+ ['hcc_result_list', 'Read evidence records submitted by this peer.',
43
+ { task_id: integer(), limit: integer(200) }, [], true],
44
+ ['hcc_result_record', 'Record local evidence for an owned task. Does not record publication, business acceptance, or task completion.',
45
+ { task_id: integer(), kind: { type: 'string', enum: ['verification', 'command', 'diff'] },
46
+ title: string(300), details: string(64000), evidence: { ...strings, maxItems: 20, items: string(2000) },
47
+ status: { type: 'string', enum: ['passed', 'failed', 'pending'] }, result_key: string(300) },
48
+ ['task_id', 'title', 'status'], false]
49
+ ];
50
+
51
+ // Pure catalogue shared by MCP and the Cordis tool registry.
52
+ export function scopedMcpToolDefinitions() {
53
+ return definitions.map(([name, description, properties, required, readOnly]) => ({
54
+ name, description, inputSchema: structuredClone({ type: 'object', properties, required, additionalProperties: false }),
55
+ annotations: { readOnlyHint: readOnly, destructiveHint: !readOnly, openWorldHint: false }
56
+ }));
57
+ }
58
+
59
+ function validate(value, schema, label) {
60
+ if (schema.type === 'object') {
61
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new CliError('BAD_ARGS', `${label} must be an object`);
62
+ for (const key of Object.keys(value)) {
63
+ if (!Object.hasOwn(schema.properties, key)) throw new CliError('BAD_ARGS', `Unknown argument: ${key}`);
64
+ validate(value[key], schema.properties[key], key);
65
+ }
66
+ for (const key of schema.required || []) if (!Object.hasOwn(value, key)) throw new CliError('BAD_ARGS', `Missing argument: ${key}`);
67
+ } else if (schema.type === 'string') {
68
+ if (typeof value !== 'string' || !value.trim() || value.length < (schema.minLength || 0) ||
69
+ value.length > schema.maxLength || value.includes('\0') || (schema.enum && !schema.enum.includes(value))) {
70
+ throw new CliError('BAD_ARGS', `${label} must be a valid bounded string`);
71
+ }
72
+ } else if (schema.type === 'integer') {
73
+ if (!Number.isSafeInteger(value) || value < schema.minimum || value > schema.maximum) throw new CliError('BAD_ARGS', `${label} must be a positive bounded integer`);
74
+ } else if (schema.type === 'boolean') {
75
+ if (typeof value !== 'boolean') throw new CliError('BAD_ARGS', `${label} must be boolean`);
76
+ } else if (schema.type === 'array') {
77
+ if (!Array.isArray(value) || value.length > schema.maxItems) throw new CliError('BAD_ARGS', `${label} must be a bounded array`);
78
+ for (const item of value) validate(item, schema.items, label);
79
+ }
80
+ }
81
+
82
+ export function createScopedMcpTools({ ctx, authority, connect: baseConnect, touchPeer, now, addEvent }) {
83
+ const peer = authority.scope.peer, scopedCtx = { ...ctx, cwd: authority.scope.root,
84
+ root: authority.scope.root, dbPath: authority.scope.dbPath, json: true };
85
+ let captured, databases = [], active = false, taskGuard = null;
86
+ function assertWrite(db) {
87
+ authority.assertOwnership(db);
88
+ if (taskGuard !== null) {
89
+ const row = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskGuard);
90
+ if (!row || row.owner !== peer) throw new CliError('TASK_OWNED', 'Task ownership changed before recording work');
91
+ }
92
+ }
93
+ function connect() {
94
+ const db = baseConnect(scopedCtx, { create: false, migrateRegistered: false });
95
+ databases.push(db);
96
+ authority.assertOwnership(db);
97
+ // Existing business functions keep their transactions. Guard each write
98
+ // inside that transaction, including commands that normally autocommit.
99
+ return new Proxy(db, { get(target, key) {
100
+ if (key === 'prepare') return sql => {
101
+ const statement = target.prepare(sql);
102
+ return new Proxy(statement, { get(stmt, property) {
103
+ if (property === 'run') return (...params) => {
104
+ const run = () => { assertWrite(target); return stmt.run(...params); };
105
+ return target.isTransaction ? run() : tx(target, run);
106
+ };
107
+ const value = stmt[property]; return typeof value === 'function' ? value.bind(stmt) : value;
108
+ } });
109
+ };
110
+ const value = target[key]; return typeof value === 'function' ? value.bind(target) : value;
111
+ } });
112
+ }
113
+ const evidence = createEvidenceRuntime({ now });
114
+ const messages = createMessageStore({ now, addEvent });
115
+ const tasks = createTaskStore({ now, addEvent, sendMessage: messages.sendMessage,
116
+ activePeerTtl: 600, observeClockSafety: evidence.observeTaskTakeoverClockSafety });
117
+ const dependencies = { connect, now, tx, addEvent, CliError, parseOpts, intOpt, required,
118
+ positiveSafeIntOpt, positiveIntOpt, wantsHelp, ACTIVE_PEER_TTL: 600, DEFAULT_LOCK_TTL: 900,
119
+ printResult: (_ctx, result) => { captured = result; },
120
+ resolveCurrentPeer: () => ({ id: peer, auto: false }),
121
+ touchCurrentPeer: (db, _ctx, identity, status) => {
122
+ if (identity.id !== peer) throw new CliError('MCP_SCOPE_INVALID', 'Peer identity changed');
123
+ authority.assertOwnership(db); touchPeer(db, peer, status);
124
+ },
125
+ iso: timestamp => new Date(timestamp * 1000).toISOString(),
126
+ ...messages, ...tasks, ...evidence,
127
+ annotateTasksWithLiveness, clockGraceSuppressed, readClockGraceUntil,
128
+ leaseDeadline, scopedLockResource, lockLabel, lockScope, locksConflict,
129
+ clockSafetyUnavailable, captureLockAcquireSubject, sameLockAcquireSubject,
130
+ observeLockOwnerEvidence, runOptimisticEvidenceMutation, normalizeListText
131
+ };
132
+ const { cmdTask, notifyTaskOwnerConflict } = createTaskCommands(dependencies);
133
+ const { cmdLock } = createLockCommands({ ...dependencies, notifyTaskOwnerConflict });
134
+ const { cmdMsg } = createMsgCommands(dependencies);
135
+ const { cmdHandoff } = createCoordinationCommands(dependencies);
136
+ const { statusSnapshot } = createCoordinationState({ connect, now, ...messages, ...tasks,
137
+ observePeerEvidence: evidence.observePeerEvidence });
138
+ const tools = scopedMcpToolDefinitions();
139
+ const byName = new Map(tools.map(tool => [tool.name, tool]));
140
+ const option = (key, value) => `--${key}=${value}`;
141
+ function assertTask(taskId, action) {
142
+ const db = connect();
143
+ const row = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId);
144
+ if (!row) throw new CliError('NOT_FOUND', 'Task does not exist');
145
+ tasks.assertTaskOwnerForMutation(db, peer, row, action);
146
+ if (row.owner !== peer) throw new CliError('TASK_OWNED', 'Claim this task before recording work or acquiring its locks');
147
+ taskGuard = taskId;
148
+ }
149
+ async function execute(name, args) {
150
+ if (name === 'hcc_state') return statusSnapshot(scopedCtx, peer);
151
+ if (name === 'hcc_inbox') return messages.queryInbox(connect(), peer, args.all === true, args.limit || 20);
152
+ if (name === 'hcc_task_list') return cmdTask(scopedCtx, ['list', option('peer', peer), option('limit', args.limit || 50), ...(args.all ? ['--all'] : [])]);
153
+ if (name === 'hcc_task_next') return cmdTask(scopedCtx, ['next', option('peer', peer), '--count=1']);
154
+ if (name === 'hcc_result_list') return listTaskResults(connect(), { peer, taskId: args.task_id || null, limit: args.limit || 100 });
155
+ if (name === 'hcc_result_record') {
156
+ assertTask(args.task_id, 'result-record');
157
+ if (args.status === 'passed' && !args.evidence?.length) throw new CliError('BAD_ARGS', 'A passed result needs an evidence reference');
158
+ return recordTaskResult(connect(), { peer, taskId: args.task_id,
159
+ executorId: authority.scope.executorId, kind: args.kind, title: args.title,
160
+ details: args.details, evidence: args.evidence, status: args.status,
161
+ resultKey: args.result_key, stage: 'local', source: 'executor' }, { now, addEvent });
162
+ }
163
+ if (name === 'hcc_message_send') return cmdMsg(scopedCtx, ['send', option('from', peer), option('to', args.to),
164
+ option('body', args.body), option('kind', args.kind || 'note'), ...(args.task_id ? [option('task', args.task_id)] : [])]);
165
+ if (name === 'hcc_handoff') {
166
+ assertTask(args.task_id, 'handoff');
167
+ return cmdHandoff(scopedCtx, ['create', option('from', peer), option('task', args.task_id), option('summary', args.summary),
168
+ option('changed-files', JSON.stringify(args.changed_files || [])), option('tests', JSON.stringify(args.tests || [])),
169
+ option('risks', JSON.stringify(args.risks || [])), ...(args.to ? [option('to', args.to)] : [])]);
170
+ }
171
+ if (name === 'hcc_lock_acquire') assertTask(args.task_id, 'lock-acquire');
172
+ return cmdLock(scopedCtx, [name === 'hcc_lock_acquire' ? 'acquire' : 'release', option('peer', peer), option('resource', args.resource),
173
+ ...(args.scope ? [option('scope', args.scope)] : []), ...(args.task_id ? [option('task', args.task_id)] : []),
174
+ ...(args.ttl ? [option('ttl', args.ttl)] : []), ...(args.reason ? [option('reason', args.reason)] : [])]);
175
+ }
176
+ return {
177
+ list: () => tools.map(tool => structuredClone(tool)), has: name => byName.has(name),
178
+ async call(name, args) {
179
+ if (active) return { isError: true, content: [{ type: 'text', text: 'A scoped tool call is already running' }] };
180
+ active = true; captured = undefined; databases = []; taskGuard = null;
181
+ try {
182
+ if (!byName.has(name)) throw new CliError('BAD_ARGS', 'Unknown scoped tool');
183
+ validate(args, byName.get(name).inputSchema, 'arguments');
184
+ authority.assertValid();
185
+ const direct = await execute(name, args);
186
+ const structuredContent = redactSecrets({ ok: true, peer, data: captured === undefined ? direct : captured });
187
+ return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent, isError: false };
188
+ } catch (error) {
189
+ const structuredContent = redactSecrets({ ok: false, peer, error: {
190
+ code: error instanceof CliError ? error.code : 'MCP_TOOL_FAILED',
191
+ message: error instanceof CliError ? error.message : 'The scoped tool call failed'
192
+ } });
193
+ return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent, isError: true };
194
+ } finally { for (const db of databases) { try { db.close(); } catch {} } active = false; }
195
+ }
196
+ };
197
+ }
@@ -98,7 +98,9 @@ function parseMacBootToken(text) {
98
98
  }
99
99
 
100
100
  function macIdentity(pid) {
101
- const boot = run('sysctl', ['-n', 'kern.boottime']);
101
+ let boot = run('sysctl', ['-n', 'kern.boottime']);
102
+ // Launchers often omit /usr/sbin from PATH; macOS still provides sysctl here.
103
+ if (boot.error?.code === 'ENOENT') boot = run('/usr/sbin/sysctl', ['-n', 'kern.boottime']);
102
104
  if (boot.error || boot.status !== 0) return null;
103
105
  const bootToken = parseMacBootToken(boot.stdout);
104
106
  if (!bootToken) return null;
@@ -2,6 +2,15 @@ export function normalizeVersion(version) {
2
2
  return String(version || '').replace(/^v/, '');
3
3
  }
4
4
 
5
+ export function isPrereleaseVersion(version) {
6
+ const numeric = '(?:0|[1-9][0-9]*)';
7
+ const identifier = `(?:${numeric}|[0-9]*[A-Za-z-][0-9A-Za-z-]*)`;
8
+ const semver = new RegExp(`^${numeric}\\.${numeric}\\.${numeric}(?:-(${identifier}(?:\\.${identifier})*))?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`);
9
+ const match = normalizeVersion(version).match(semver);
10
+ if (!match) throw new Error(`Invalid release version: ${version}`);
11
+ return match[1] !== undefined;
12
+ }
13
+
5
14
  export function releaseSection(markdown, version) {
6
15
  const lines = markdown.split(/\r?\n/);
7
16
  const heading = `## ${version}`;
@@ -0,0 +1,38 @@
1
+ import http from 'node:http';
2
+ import { CliError } from '../../shared/errors.mjs';
3
+ import { readNativePointer } from './store.mjs';
4
+
5
+ export function nativeRequest(ctx, method, route, body = null, { timeoutMs = 35000 } = {}) {
6
+ const pointer = readNativePointer(ctx);
7
+ if (!pointer) return Promise.reject(new CliError('NATIVE_RUNTIME_OFFLINE', 'Native runtime is offline. Run hcc native up.'));
8
+ return new Promise((resolve, reject) => {
9
+ const payload = body === null ? null : JSON.stringify(body);
10
+ let timer;
11
+ const fail = (error) => { clearTimeout(timer); reject(error); };
12
+ const invalid = (message) => new CliError('NATIVE_RESPONSE_INVALID', message, { uncertain: method !== 'GET' });
13
+ const request = http.request({ host: '127.0.0.1', port: pointer.port, method, path: route,
14
+ headers: { authorization: `Bearer ${pointer.token}`, ...(payload ? { 'content-type': 'application/json', 'content-length': Buffer.byteLength(payload) } : {}) }
15
+ }, (response) => {
16
+ let data = '';
17
+ response.setEncoding('utf8');
18
+ response.on('data', (chunk) => {
19
+ data += chunk;
20
+ if (Buffer.byteLength(data) > 8 * 1024 * 1024) response.destroy(invalid('Native response is too large'));
21
+ });
22
+ response.on('error', (error) => fail(error instanceof CliError ? error : invalid('Native response was interrupted; inspect status before retrying.')));
23
+ response.on('end', () => {
24
+ clearTimeout(timer);
25
+ try {
26
+ const result = JSON.parse(data);
27
+ if (!result || typeof result !== 'object' || Array.isArray(result) || typeof result.ok !== 'boolean') throw invalid('Invalid native response; inspect status before retrying.');
28
+ if (!result.ok) fail(new CliError(result.error?.code || 'NATIVE_REQUEST_FAILED', result.error?.message || 'Native request failed', result.error?.extra || {}));
29
+ else if (response.statusCode < 200 || response.statusCode >= 300) fail(invalid('Native runtime returned an unsuccessful HTTP response'));
30
+ else resolve(result.data);
31
+ } catch (error) { fail(error instanceof CliError ? error : invalid('Invalid native JSON response; inspect status before retrying.')); }
32
+ });
33
+ });
34
+ timer = setTimeout(() => request.destroy(new CliError('NATIVE_CLIENT_TIMEOUT', 'Native runtime request timed out; inspect status before retrying.', { uncertain: method !== 'GET' })), timeoutMs);
35
+ request.on('error', (error) => fail(error instanceof CliError ? error : new CliError('NATIVE_RUNTIME_OFFLINE', 'Cannot reach the native runtime', { uncertain: method !== 'GET' })));
36
+ request.end(payload);
37
+ });
38
+ }