@logicseek/hello-cc 1.0.5 → 1.1.0-rc.3
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 +72 -0
- package/docs/codex-account.zh-CN.md +38 -0
- package/docs/codex-protocol.md +59 -0
- package/docs/commands.md +9 -0
- package/docs/commands.zh-CN.md +7 -0
- package/docs/macos-process-identity.md +2 -0
- package/docs/native.md +179 -14
- package/docs/native.zh-CN.md +136 -6
- package/docs/web-handoff.zh-CN.md +118 -8
- package/lib/cli/app.mjs +47 -4
- package/lib/cli/commands/coordination.mjs +14 -4
- package/lib/cli/commands/dsh.mjs +1 -1
- package/lib/cli/commands/gc.mjs +22 -15
- package/lib/cli/commands/hook.mjs +19 -3
- package/lib/cli/commands/install.mjs +80 -10
- package/lib/cli/commands/misc.mjs +36 -9
- package/lib/cli/commands/native.mjs +4 -38
- package/lib/cli/commands/peer.mjs +3 -0
- package/lib/cli/commands/run.mjs +13 -6
- package/lib/cli/commands/tmux.mjs +1 -2
- package/lib/cli-runtime.mjs +19 -2
- package/lib/core/coordination/results.mjs +27 -10
- package/lib/core/sessions/hook-root-identity.mjs +47 -0
- package/lib/core/sessions/launch.mjs +7 -1
- package/lib/db/connection.mjs +140 -42
- package/lib/db/schema.mjs +59 -0
- package/lib/diagnostics/codex-protocol.mjs +105 -0
- package/lib/guidance.mjs +21 -36
- package/lib/integrations/codex-account.mjs +143 -0
- package/lib/integrations/codex-interactions.mjs +13 -11
- package/lib/integrations/dsh-collaboration.mjs +6 -12
- package/lib/integrations/dsh-cordis.mjs +17 -13
- package/lib/integrations/dsh.mjs +16 -11
- package/lib/integrations/mcp-elicitation.mjs +116 -0
- package/lib/integrations/mcp-url-elicitation.mjs +25 -0
- package/lib/integrations/native/claude.mjs +24 -6
- package/lib/integrations/native/codex.mjs +38 -7
- package/lib/integrations/native/dsh-acp.mjs +67 -24
- package/lib/integrations/native/index.mjs +4 -1
- package/lib/integrations/native/interactions.mjs +2 -1
- package/lib/integrations/native/jsonrpc.mjs +37 -5
- package/lib/integrations/native/telemetry.mjs +30 -0
- package/lib/process/identity.mjs +4 -0
- package/lib/process/pinned-cwd.mjs +302 -0
- package/lib/process/pinned-guidance.mjs +138 -0
- package/lib/process/pinned-project-state.mjs +105 -0
- package/lib/process/selected-cwd-identity.mjs +95 -0
- package/lib/runtime/buffer-evidence.mjs +2 -1
- package/lib/runtime/client.mjs +9 -0
- package/lib/runtime/native/client.mjs +4 -1
- package/lib/runtime/native/history.mjs +140 -0
- package/lib/runtime/native/launcher.mjs +106 -0
- package/lib/runtime/native/service.mjs +64 -8
- package/lib/runtime/native/store.mjs +83 -58
- package/lib/runtime/paths.mjs +17 -2
- package/lib/runtime/private-file.mjs +76 -0
- package/lib/runtime/private-state.mjs +546 -0
- package/lib/runtime/project-context.mjs +2 -1
- package/lib/runtime/project-path.mjs +92 -20
- package/lib/runtime/project-trust.mjs +79 -0
- package/lib/runtime/projects.mjs +9 -3
- package/lib/runtime/state-migration.mjs +697 -0
- package/lib/runtime/state.mjs +69 -27
- package/lib/shared/automatic-result-key.mjs +6 -0
- package/lib/shared/json-file.mjs +55 -6
- package/lib/shared/redact.mjs +2 -1
- package/lib/ui/help.mjs +6 -4
- package/lib/web/agent-defaults.mjs +110 -0
- package/lib/web/auto-attach.mjs +3 -2
- package/lib/web/browser/contracts.d.ts +10 -0
- package/lib/web/browser/core.mjs +1997 -0
- package/lib/web/browser/project-data.mjs +158 -0
- package/lib/web/browser/session-sync.mjs +82 -0
- package/lib/web/browser-assets.mjs +19 -0
- package/lib/web/buffer-gc-runtime.mjs +2 -2
- package/lib/web/codex-app-server.mjs +111 -37
- package/lib/web/codex-sessions.mjs +61 -12
- package/lib/web/context-files.mjs +82 -0
- package/lib/web/cookie-auth.mjs +51 -3
- package/lib/web/external-sessions.mjs +76 -9
- package/lib/web/http-routes.mjs +238 -25
- package/lib/web/http.mjs +50 -4
- package/lib/web/liveness-reaper.mjs +2 -2
- package/lib/web/native-sessions.mjs +323 -14
- package/lib/web/project-contexts.mjs +236 -44
- package/lib/web/project-file-write.mjs +249 -0
- package/lib/web/project-files.mjs +268 -0
- package/lib/web/pty-sessions.mjs +11 -3
- package/lib/web/runtime-main.mjs +132 -80
- package/lib/web/session-serialize.mjs +87 -8
- package/lib/web/session-state-sync.mjs +98 -0
- package/lib/web/shutdown-diagnostics.mjs +38 -0
- package/lib/web/shutdown-response.mjs +21 -0
- package/lib/web/shutdown-state.mjs +91 -0
- package/lib/web/startup.mjs +103 -35
- package/lib/web/task-results.mjs +19 -19
- package/lib/web/tls.mjs +84 -33
- package/lib/web/tmux-sessions.mjs +73 -9
- package/lib/web/tmux-stream.mjs +36 -8
- package/lib/web/ui-agent-defaults.mjs +149 -0
- package/lib/web/ui-agent-start.mjs +330 -0
- package/lib/web/ui-codex-account.mjs +81 -0
- package/lib/web/ui-codex.mjs +105 -85
- package/lib/web/ui-command-palette.mjs +21 -5
- package/lib/web/ui-file-writes.mjs +220 -0
- package/lib/web/ui-files.mjs +231 -0
- package/lib/web/ui-handoff.mjs +10 -2
- package/lib/web/ui-history.mjs +175 -105
- package/lib/web/ui-i18n.mjs +280 -8
- package/lib/web/ui-interactions.mjs +107 -6
- package/lib/web/ui-native-timeline.mjs +326 -0
- package/lib/web/ui-native.mjs +93 -43
- package/lib/web/ui-review.mjs +5 -3
- package/lib/web/ui-safe-markdown.mjs +79 -0
- package/lib/web/ui-session-tools.mjs +224 -0
- package/lib/web/ui-template.mjs +29 -1968
- package/lib/web/ui-terminal-find.mjs +5 -3
- package/lib/web/ui-workbench.mjs +5 -3
- package/lib/web/ui-workspace.mjs +3 -0
- package/package.json +8 -2
- package/scripts/benchmark-session-sync.mjs +53 -0
- package/scripts/codex-protocol.mjs +88 -0
- package/scripts/dsh-approval-layers-acceptance.mjs +172 -0
- package/scripts/dsh-package-provenance.mjs +118 -0
- package/scripts/mcp-form-live-fixture.mjs +81 -0
- package/scripts/native-bounded-live-acceptance.mjs +193 -0
- package/scripts/native-fixture-root.mjs +49 -0
- package/scripts/native-installed-acceptance.mjs +611 -0
- package/scripts/native-stability-checks.mjs +40 -0
- package/scripts/regression-cookie-expiry.mjs +70 -0
- package/scripts/regression-http-transport.mjs +9 -0
- package/scripts/regression-terminal-marker.mjs +95 -0
- package/scripts/regression.mjs +481 -219
- package/scripts/shim-reentry-probe.mjs +105 -0
- package/scripts/web-acceptance-diagnostics.mjs +90 -0
- package/scripts/web-codex-account-acceptance.mjs +178 -0
- package/scripts/web-file-preview-checks.mjs +302 -0
- package/scripts/web-mcp-form-acceptance.mjs +134 -0
- package/scripts/web-mcp-form-live-acceptance.mjs +368 -0
- package/scripts/web-native-handoff-checks.mjs +128 -0
- package/scripts/web-native-interaction-acceptance.mjs +219 -18
- package/scripts/web-native-interaction-diagnostics.mjs +80 -0
- package/scripts/web-native-model-acceptance.mjs +22 -7
- package/scripts/web-session-tools-checks.mjs +112 -0
- package/scripts/web-workbench-acceptance.mjs +435 -0
- package/scripts/web-workbench-installed-acceptance.mjs +122 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
|
|
3
|
+
// Explicit limits bound opt-in model consumption and idle observation time.
|
|
4
|
+
export function stabilityOptions(args) {
|
|
5
|
+
const fields = [
|
|
6
|
+
['--stability-cycles', 'cycles', 6, 1, 24],
|
|
7
|
+
['--stability-burst', 'burst', 2, 1, 4],
|
|
8
|
+
['--stability-idle-ms', 'idleMs', 30000, 0, 300000],
|
|
9
|
+
['--stability-resume-every', 'resumeEvery', 2, 1, 24]
|
|
10
|
+
];
|
|
11
|
+
const enabled = args.includes('--stability');
|
|
12
|
+
assert.ok(!args.includes('--stability-only') || enabled, '--stability-only requires --stability');
|
|
13
|
+
const config = {};
|
|
14
|
+
for (const [flag, key, fallback, min, max] of fields) {
|
|
15
|
+
const indices = args.flatMap((value, index) => value === flag ? [index] : []);
|
|
16
|
+
assert.ok(indices.length <= 1, `${flag} must not be repeated`);
|
|
17
|
+
assert.ok(enabled || !indices.length, `${flag} requires --stability`);
|
|
18
|
+
const raw = indices.length ? args[indices[0] + 1] : String(fallback);
|
|
19
|
+
assert.ok(typeof raw === 'string' && /^\d+$/.test(raw), `${flag} requires an integer`);
|
|
20
|
+
const value = Number(raw);
|
|
21
|
+
assert.ok(Number.isSafeInteger(value) && value >= min && value <= max, `${flag} must be ${min}..${max}`);
|
|
22
|
+
config[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return enabled ? config : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function stabilityPayload(text) {
|
|
28
|
+
assert.equal(typeof text, 'string');
|
|
29
|
+
const tuples = text.match(/\bHCC_STABILITY_(?:CODEX|CLAUDE|DSH)_[0-9a-f]{16}\|(?:SEED|T\d+_\d+_[0-9a-f]{8})(?:\|(?:SEED|T\d+_\d+_[0-9a-f]{8}))?/g) || [];
|
|
30
|
+
assert.equal(tuples.length, 1, 'reply must contain one unambiguous stability tuple');
|
|
31
|
+
return tuples[0];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assertStabilityEvidence(evidence) {
|
|
35
|
+
assert.equal(evidence.state, 'completed', 'stability work must complete');
|
|
36
|
+
assert.equal(evidence.replyCount, 1, 'stability work must have exactly one reply');
|
|
37
|
+
assert.equal(evidence.ackCount, 1, 'stability work must have exactly one ACK');
|
|
38
|
+
assert.equal(evidence.replyThread, evidence.messageId, 'reply must retain its request thread');
|
|
39
|
+
assert.equal(stabilityPayload(evidence.actual), evidence.expected, 'original context and FIFO predecessor must match');
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
|
+
|
|
4
|
+
const CLOSE_TIMEOUT_MS = 5_000;
|
|
5
|
+
|
|
6
|
+
// Fixture-only deadlines: cookie validity remains the server's wall-clock
|
|
7
|
+
// decision. Clock correction must not make a monotonic sleep prove expiry.
|
|
8
|
+
export function createCookieExpiryWindow(ttlMs, {
|
|
9
|
+
monotonicNow = () => performance.now(), wait = sleep,
|
|
10
|
+
setTimer = setTimeout, clearTimer = clearTimeout
|
|
11
|
+
} = {}) {
|
|
12
|
+
const startedAt = monotonicNow();
|
|
13
|
+
const deadline = startedAt + ttlMs + CLOSE_TIMEOUT_MS;
|
|
14
|
+
let lastStatus = null;
|
|
15
|
+
|
|
16
|
+
function timeout(phase) {
|
|
17
|
+
return Object.assign(new Error(`cookie expiry ${phase} exceeded the original deadline`), {
|
|
18
|
+
code: 'COOKIE_EXPIRY_TIMEOUT',
|
|
19
|
+
extra: { elapsedMs: Math.round(monotonicNow() - startedAt), status: lastStatus }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function bounded(operation, until, phase) {
|
|
24
|
+
const remaining = until - monotonicNow();
|
|
25
|
+
if (remaining <= 0) throw timeout(phase);
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
let timer;
|
|
28
|
+
const expired = new Promise((_, reject) => {
|
|
29
|
+
timer = setTimer(() => {
|
|
30
|
+
const error = timeout(phase);
|
|
31
|
+
reject(error);
|
|
32
|
+
controller.abort(error);
|
|
33
|
+
}, remaining);
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
const value = await Promise.race([Promise.resolve().then(() => operation(controller.signal)), expired]);
|
|
37
|
+
if (monotonicNow() >= until) throw timeout(phase);
|
|
38
|
+
return value;
|
|
39
|
+
} finally { clearTimer(timer); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
prepare(operation) { return bounded(operation, deadline, 'setup'); },
|
|
44
|
+
async waitForHttpExpiry(probe) {
|
|
45
|
+
// Retain the original quiet TTL wait, but count it against the same
|
|
46
|
+
// overall deadline. Only poll when wall-clock expiry lags behind it.
|
|
47
|
+
await bounded(() => wait(Math.max(0, startedAt + ttlMs - monotonicNow())), deadline, 'initial wait');
|
|
48
|
+
for (;;) {
|
|
49
|
+
const response = await bounded(async signal => {
|
|
50
|
+
const value = await probe(signal);
|
|
51
|
+
lastStatus = value.status;
|
|
52
|
+
if (![200, 401].includes(lastStatus)) {
|
|
53
|
+
throw Object.assign(new Error(`cookie expiry HTTP returned unexpected status ${lastStatus}`), {
|
|
54
|
+
code: 'COOKIE_EXPIRY_HTTP_STATUS', extra: { status: lastStatus }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
await value.body?.cancel();
|
|
58
|
+
return value;
|
|
59
|
+
}, deadline, 'HTTP check');
|
|
60
|
+
if (lastStatus === 401) return response;
|
|
61
|
+
const remaining = deadline - monotonicNow();
|
|
62
|
+
if (remaining <= 0) throw timeout('HTTP check');
|
|
63
|
+
await wait(Math.min(100, remaining));
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
waitForClose(closed) {
|
|
67
|
+
return bounded(() => closed, Math.min(deadline, monotonicNow() + CLOSE_TIMEOUT_MS), 'WebSocket close');
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Fixture traffic alternates HTTP requests with synchronous CLI work. Close
|
|
2
|
+
// each response's connection so later requests cannot inherit its idle socket.
|
|
3
|
+
// Use this from the fixture's first request: it cannot isolate a socket pool
|
|
4
|
+
// already populated by other fetch callers. Never replay uncertain mutations.
|
|
5
|
+
export function fixtureFetch(input, options = {}) {
|
|
6
|
+
const headers = new Headers(options.headers);
|
|
7
|
+
headers.set('Connection', 'close');
|
|
8
|
+
return globalThis.fetch(input, { ...options, headers });
|
|
9
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Terminal data frames are arbitrary stream chunks. Projections can be checked
|
|
2
|
+
// independently, but must never supply a prefix to the raw stream matcher.
|
|
3
|
+
const failures = new WeakMap();
|
|
4
|
+
|
|
5
|
+
// Only errors created here carry publishable diagnostics. Never publish a
|
|
6
|
+
// socket error's message, terminal contents, token, or caller-supplied fields.
|
|
7
|
+
export function terminalMarkerFailureDiagnostic(error) {
|
|
8
|
+
return failures.get(error);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createTerminalMarkerMatcher(marker) {
|
|
12
|
+
if (typeof marker !== 'string' || !marker) throw new TypeError('A nonempty terminal marker is required');
|
|
13
|
+
const limit = marker.length - 1;
|
|
14
|
+
let suffix = '';
|
|
15
|
+
return {
|
|
16
|
+
get retainedLength() { return suffix.length; },
|
|
17
|
+
observe(type, value) {
|
|
18
|
+
if (!['snapshot', 'replace', 'data'].includes(type)) return false;
|
|
19
|
+
const data = String(value || '');
|
|
20
|
+
if (type !== 'data') return data.includes(marker);
|
|
21
|
+
const matched = data.includes(marker) || (suffix + data.slice(0, limit)).includes(marker);
|
|
22
|
+
suffix = limit === 0 ? '' : data.length >= limit
|
|
23
|
+
? data.slice(-limit) : (suffix + data).slice(-limit);
|
|
24
|
+
return matched;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function waitForTerminalMarker(ws, marker, { claim, release, timeoutMs = 15_000 }) {
|
|
30
|
+
const matcher = createTerminalMarkerMatcher(marker);
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
let settled = false, sawMarker = false, inputStarted = false, inputSent = false;
|
|
34
|
+
let phase = 'snapshot';
|
|
35
|
+
const timer = setTimeout(() => finish('TERMINAL_MARKER_TIMEOUT'), timeoutMs);
|
|
36
|
+
const removeSocketListeners = () => {
|
|
37
|
+
ws.off('error', onError);
|
|
38
|
+
ws.off('close', onClose);
|
|
39
|
+
};
|
|
40
|
+
function finish(code = null) {
|
|
41
|
+
if (settled) return;
|
|
42
|
+
settled = true;
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
ws.off('message', onMessage);
|
|
45
|
+
controller.abort();
|
|
46
|
+
// Keep the error listener until close: terminate() while connecting can
|
|
47
|
+
// emit an asynchronous error before its close event.
|
|
48
|
+
if (ws.readyState === 3) removeSocketListeners();
|
|
49
|
+
try {
|
|
50
|
+
if (code) ws.terminate();
|
|
51
|
+
else { try { release(); } catch {} ws.close(); }
|
|
52
|
+
} catch { try { ws.terminate(); } catch {} }
|
|
53
|
+
if (code) {
|
|
54
|
+
const error = Object.assign(new Error(`${code} (${phase})`), { code, phase });
|
|
55
|
+
failures.set(error, Object.freeze({ code, phase }));
|
|
56
|
+
reject(error);
|
|
57
|
+
} else resolve();
|
|
58
|
+
}
|
|
59
|
+
function onError() { finish('TERMINAL_MARKER_SOCKET_ERROR'); }
|
|
60
|
+
function onClose() {
|
|
61
|
+
if (!settled) finish('TERMINAL_MARKER_CLOSED');
|
|
62
|
+
removeSocketListeners();
|
|
63
|
+
}
|
|
64
|
+
function onMessage(raw) {
|
|
65
|
+
let message;
|
|
66
|
+
try { message = JSON.parse(String(raw)); }
|
|
67
|
+
catch { finish('TERMINAL_MARKER_MESSAGE_INVALID'); return; }
|
|
68
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)) { finish('TERMINAL_MARKER_MESSAGE_INVALID'); return; }
|
|
69
|
+
if (message.type === 'error') { finish('TERMINAL_MARKER_SERVER_ERROR'); return; }
|
|
70
|
+
// Initial history is not evidence that this connection's input worked.
|
|
71
|
+
// A snapshot received again must never submit the command again.
|
|
72
|
+
if (inputSent) sawMarker ||= matcher.observe(message.type, message.data);
|
|
73
|
+
if (sawMarker) { finish(); return; }
|
|
74
|
+
if (message.type !== 'snapshot' || inputStarted) return;
|
|
75
|
+
inputStarted = true;
|
|
76
|
+
phase = 'control';
|
|
77
|
+
Promise.resolve().then(() => {
|
|
78
|
+
if (settled) return;
|
|
79
|
+
return claim(controller.signal);
|
|
80
|
+
}).then(() => {
|
|
81
|
+
if (settled) return;
|
|
82
|
+
phase = 'input';
|
|
83
|
+
try {
|
|
84
|
+
inputSent = true;
|
|
85
|
+
ws.send(JSON.stringify({ type: 'input', data: `echo ${marker}\r`,
|
|
86
|
+
action_token: ws.hccActionToken, epoch: ws.hccControl?.epoch }));
|
|
87
|
+
if (!settled) phase = 'marker';
|
|
88
|
+
} catch { finish('TERMINAL_MARKER_INPUT_FAILED'); }
|
|
89
|
+
}).catch(() => finish('TERMINAL_MARKER_CONTROL_FAILED'));
|
|
90
|
+
}
|
|
91
|
+
ws.on('message', onMessage);
|
|
92
|
+
ws.on('error', onError);
|
|
93
|
+
ws.on('close', onClose);
|
|
94
|
+
});
|
|
95
|
+
}
|