@oatelauser/crosschat 1.0.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/CHANGELOG.md +22 -0
- package/LICENSE +25 -0
- package/README.md +244 -0
- package/dist/claude/deliver.js +33 -0
- package/dist/claude/deliver.js.map +1 -0
- package/dist/claude/frame.js +16 -0
- package/dist/claude/frame.js.map +1 -0
- package/dist/claude/key-file.js +45 -0
- package/dist/claude/key-file.js.map +1 -0
- package/dist/claude/registry.js +79 -0
- package/dist/claude/registry.js.map +1 -0
- package/dist/claude/resolve-exe.js +31 -0
- package/dist/claude/resolve-exe.js.map +1 -0
- package/dist/cli.js +270 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex/client.js +130 -0
- package/dist/codex/client.js.map +1 -0
- package/dist/codex/deliver.js +162 -0
- package/dist/codex/deliver.js.map +1 -0
- package/dist/codex/discovery.js +45 -0
- package/dist/codex/discovery.js.map +1 -0
- package/dist/codex/queue.js +71 -0
- package/dist/codex/queue.js.map +1 -0
- package/dist/codex/rollout-meta.js +118 -0
- package/dist/codex/rollout-meta.js.map +1 -0
- package/dist/codex/rpc.js +153 -0
- package/dist/codex/rpc.js.map +1 -0
- package/dist/codex/transport.js +357 -0
- package/dist/codex/transport.js.map +1 -0
- package/dist/commands/claude-wrapper.js +28 -0
- package/dist/commands/claude-wrapper.js.map +1 -0
- package/dist/commands/doctor.js +169 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install-skills.js +26 -0
- package/dist/commands/install-skills.js.map +1 -0
- package/dist/commands/send.js +122 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/status.js +77 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/envelope.js +34 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -0
- package/dist/identity.js +42 -0
- package/dist/identity.js.map +1 -0
- package/dist/outbox.js +172 -0
- package/dist/outbox.js.map +1 -0
- package/dist/platform/pipe-transport.js +73 -0
- package/dist/platform/pipe-transport.js.map +1 -0
- package/dist/platform/process-inspector.js +15 -0
- package/dist/platform/process-inspector.js.map +1 -0
- package/dist/rate-limit.js +49 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/ref.js +64 -0
- package/dist/ref.js.map +1 -0
- package/dist/resolve.js +30 -0
- package/dist/resolve.js.map +1 -0
- package/package.json +47 -0
- package/skills/crosschat/SKILL.md +65 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { MultichatError } from '../errors.js';
|
|
3
|
+
import { checkAndRecord, rateKey } from '../rate-limit.js';
|
|
4
|
+
import { park } from '../outbox.js';
|
|
5
|
+
import { endpointOfIdentity, identityKey, identityMatchesEndpoint, resolveCallerIdentity, } from '../identity.js';
|
|
6
|
+
import { resolveTargetByName } from '../resolve.js';
|
|
7
|
+
import { composeEnvelope } from '../envelope.js';
|
|
8
|
+
import { decodeRef, encodeRef, newConversationRef, nextTurnRef, } from '../ref.js';
|
|
9
|
+
/** Hard body limit (ticket 004): 16KiB, enforced synchronously, never truncated. */
|
|
10
|
+
export const MAX_BODY_BYTES = 16_384;
|
|
11
|
+
export async function runSend(args, deps) {
|
|
12
|
+
resolveTargetArgs(args);
|
|
13
|
+
const body = resolveBody(args, deps);
|
|
14
|
+
const size = Buffer.byteLength(body, 'utf8');
|
|
15
|
+
if (size > MAX_BODY_BYTES) {
|
|
16
|
+
throw new MultichatError('MESSAGE_TOO_LARGE', `Body is ${size} bytes; the limit is ${MAX_BODY_BYTES}. Write the content to a file and send the path instead.`);
|
|
17
|
+
}
|
|
18
|
+
const scan = deps.listClaudeSessions();
|
|
19
|
+
const caller = resolveCallerIdentity(deps.env, scan);
|
|
20
|
+
let target;
|
|
21
|
+
let newRef;
|
|
22
|
+
let turn;
|
|
23
|
+
if (args.conversation !== undefined) {
|
|
24
|
+
const ref = decodeRef(args.conversation);
|
|
25
|
+
const asFrom = identityMatchesEndpoint(caller, ref.f);
|
|
26
|
+
const asTo = identityMatchesEndpoint(caller, ref.t);
|
|
27
|
+
if (!asFrom && !asTo) {
|
|
28
|
+
throw new MultichatError('CALLER_NOT_IN_CONVERSATION', `Caller (${identityKey(caller)}) is not an endpoint of this conversation.`);
|
|
29
|
+
}
|
|
30
|
+
target = asFrom ? ref.t : ref.f;
|
|
31
|
+
if (target.p === 'human') {
|
|
32
|
+
throw new MultichatError('CANNOT_REPLY_TO_HUMAN', 'This conversation was started by a human; there is no agent session to deliver a reply to.');
|
|
33
|
+
}
|
|
34
|
+
turn = ref.c + 1;
|
|
35
|
+
newRef = nextTurnRef(ref);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const threads = await deps.listCodexThreads();
|
|
39
|
+
// Guard above ensures exactly one of --to/--conversation; here it is --to.
|
|
40
|
+
const resolved = resolveTargetByName(args.to ?? '', scan.sessions, threads);
|
|
41
|
+
target =
|
|
42
|
+
resolved.side === 'claude'
|
|
43
|
+
? { p: 'claude', id: resolved.session.sessionId }
|
|
44
|
+
: { p: 'codex', id: resolved.thread.id };
|
|
45
|
+
newRef = newConversationRef(endpointOfIdentity(caller), target);
|
|
46
|
+
turn = 1;
|
|
47
|
+
}
|
|
48
|
+
const replyRef = encodeRef(newRef);
|
|
49
|
+
const fromName = displayName(caller);
|
|
50
|
+
if (target.p === 'claude') {
|
|
51
|
+
const claudeSession = scan.sessions.find((session) => session.sessionId === target.id);
|
|
52
|
+
if (claudeSession === undefined) {
|
|
53
|
+
throw new MultichatError('TARGET_NOT_FOUND', `Claude session ${target.id} is not currently routable (process exited or unregistered).`);
|
|
54
|
+
}
|
|
55
|
+
checkAndRecord(deps.rateDir, rateKey(identityKey(caller), `claude:${target.id}`), deps.now());
|
|
56
|
+
const toName = claudeSession.name ?? shortId('claude', claudeSession.sessionId);
|
|
57
|
+
const content = composeEnvelope({ fromName, toName, turn, ref: replyRef, body });
|
|
58
|
+
await deps.deliverClaude({ pid: claudeSession.pid, messagingSocketPath: claudeSession.messagingSocketPath }, content);
|
|
59
|
+
return formatDelivery(args.json === true, toName, turn, replyRef);
|
|
60
|
+
}
|
|
61
|
+
// Codex delivers by threadId; a missing thread surfaces as a codex adapter error.
|
|
62
|
+
checkAndRecord(deps.rateDir, rateKey(identityKey(caller), `codex:${target.id}`), deps.now());
|
|
63
|
+
const threads = await deps.listCodexThreads();
|
|
64
|
+
const threadName = threads.find((thread) => thread.id === target.id)?.name;
|
|
65
|
+
const toName = threadName ?? shortId('codex', target.id);
|
|
66
|
+
const content = composeEnvelope({ fromName, toName, turn, ref: replyRef, body });
|
|
67
|
+
try {
|
|
68
|
+
await deps.deliverCodex(target.id, content);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// Busy/locked = definitively not delivered (deliver.ts polls before giving
|
|
72
|
+
// up): park the composed envelope verbatim; the next CLI call drains it.
|
|
73
|
+
if (err instanceof MultichatError &&
|
|
74
|
+
(err.code === 'CODEX_THREAD_BUSY_TIMEOUT' || err.code === 'CODEX_THREAD_LOCKED')) {
|
|
75
|
+
park(deps.outboxDir, target.id, { envelope: content, toName, callerKey: identityKey(caller) }, deps.now());
|
|
76
|
+
return formatParked(args.json === true, toName, turn, replyRef);
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
return formatDelivery(args.json === true, toName, turn, replyRef);
|
|
81
|
+
}
|
|
82
|
+
function resolveTargetArgs(args) {
|
|
83
|
+
if (args.to !== undefined && args.conversation !== undefined) {
|
|
84
|
+
throw new MultichatError('TARGET_CONFLICT', 'Use either --to <name> or --conversation <ref>, not both.');
|
|
85
|
+
}
|
|
86
|
+
if (args.to === undefined && args.conversation === undefined) {
|
|
87
|
+
throw new MultichatError('TARGET_REQUIRED', 'Specify a target: --to <name> or --conversation <ref>.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function resolveBody(args, deps) {
|
|
91
|
+
if (args.bodyArg !== undefined && deps.stdinText !== undefined) {
|
|
92
|
+
throw new MultichatError('BODY_CONFLICT', 'Message body was given both via --body and stdin; pick one.');
|
|
93
|
+
}
|
|
94
|
+
const body = args.bodyArg ?? deps.stdinText;
|
|
95
|
+
if (body === undefined || body === '') {
|
|
96
|
+
throw new MultichatError('BODY_REQUIRED', 'Message body is required: pass --body "<text>" or pipe the message on stdin.');
|
|
97
|
+
}
|
|
98
|
+
return body;
|
|
99
|
+
}
|
|
100
|
+
function displayName(identity) {
|
|
101
|
+
if (identity.p === 'claude')
|
|
102
|
+
return identity.name ?? shortId('claude', identity.id);
|
|
103
|
+
if (identity.p === 'codex')
|
|
104
|
+
return shortId('codex', identity.id);
|
|
105
|
+
return 'human';
|
|
106
|
+
}
|
|
107
|
+
function shortId(prefix, id) {
|
|
108
|
+
return `${prefix}/${id.slice(0, 8)}`;
|
|
109
|
+
}
|
|
110
|
+
function formatDelivery(json, toName, turn, replyRef) {
|
|
111
|
+
if (json) {
|
|
112
|
+
return JSON.stringify({ status: 'delivered', to: toName, turn, replyRef });
|
|
113
|
+
}
|
|
114
|
+
return `delivered to ${toName} (turn ${turn})\nreply-ref: ${replyRef}`;
|
|
115
|
+
}
|
|
116
|
+
function formatParked(json, toName, turn, replyRef) {
|
|
117
|
+
if (json) {
|
|
118
|
+
return JSON.stringify({ status: 'parked', to: toName, turn, replyRef });
|
|
119
|
+
}
|
|
120
|
+
return `parked to ${toName} (busy; 将在对方空闲后由任意 crosschat 调用自动补投)\nreply-ref: ${replyRef}`;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/commands/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { MultichatError } from '../errors.js';
|
|
2
|
+
/** Last two segments of a session cwd (`D:\workspace\CC\foo` -> `CC\foo`). */
|
|
3
|
+
export function formatCwdShort(cwd) {
|
|
4
|
+
if (!cwd)
|
|
5
|
+
return '-';
|
|
6
|
+
const parts = cwd.split(/[\\/]+/).filter((segment) => segment.length > 0);
|
|
7
|
+
if (parts.length === 0)
|
|
8
|
+
return '-';
|
|
9
|
+
return parts.slice(-2).join(cwd.includes('\\') ? '\\' : '/');
|
|
10
|
+
}
|
|
11
|
+
/** `MM-DD HH:mm` (local time) from an ISO timestamp; `-` when unknown/invalid. */
|
|
12
|
+
export function formatCreatedAt(iso) {
|
|
13
|
+
if (!iso)
|
|
14
|
+
return '-';
|
|
15
|
+
const date = new Date(iso);
|
|
16
|
+
if (Number.isNaN(date.getTime()))
|
|
17
|
+
return '-';
|
|
18
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
19
|
+
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
20
|
+
}
|
|
21
|
+
export async function runStatus(deps, json) {
|
|
22
|
+
const scan = deps.listClaudeSessions();
|
|
23
|
+
let threads;
|
|
24
|
+
let codexError;
|
|
25
|
+
try {
|
|
26
|
+
threads = await deps.listCodexThreads();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
codexError =
|
|
30
|
+
err instanceof MultichatError
|
|
31
|
+
? `${err.code}: ${err.message}`
|
|
32
|
+
: `INTERNAL: ${err instanceof Error ? err.message : String(err)}`;
|
|
33
|
+
}
|
|
34
|
+
if (json) {
|
|
35
|
+
return JSON.stringify({
|
|
36
|
+
claude: scan.sessions.map((session) => ({
|
|
37
|
+
name: session.name ?? null,
|
|
38
|
+
kind: session.kind,
|
|
39
|
+
status: session.status,
|
|
40
|
+
pid: session.pid,
|
|
41
|
+
cwd: session.cwd ?? null,
|
|
42
|
+
})),
|
|
43
|
+
codex: threads === undefined
|
|
44
|
+
? { error: codexError }
|
|
45
|
+
: threads.map((thread) => ({
|
|
46
|
+
name: thread.name,
|
|
47
|
+
status: thread.status,
|
|
48
|
+
id: thread.id.slice(0, 8),
|
|
49
|
+
cwd: thread.meta?.cwd ?? null,
|
|
50
|
+
createdAt: thread.meta?.createdAt ?? null,
|
|
51
|
+
originator: thread.meta?.originator ?? null,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const lines = ['claude:'];
|
|
56
|
+
if (scan.sessions.length === 0)
|
|
57
|
+
lines.push(' (none)');
|
|
58
|
+
for (const session of scan.sessions) {
|
|
59
|
+
lines.push(` ${(session.name ?? '(unnamed)').padEnd(24)} ${session.kind.padEnd(12)} ${session.status.padEnd(16)} pid ${String(session.pid).padEnd(6)} ${formatCwdShort(session.cwd)}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push('codex:');
|
|
62
|
+
if (codexError !== undefined)
|
|
63
|
+
lines.push(` unavailable (${codexError})`);
|
|
64
|
+
else if ((threads ?? []).length === 0)
|
|
65
|
+
lines.push(' (none)');
|
|
66
|
+
else {
|
|
67
|
+
for (const thread of threads ?? []) {
|
|
68
|
+
const name = (thread.name ?? '(unnamed)').padEnd(24);
|
|
69
|
+
const dir = formatCwdShort(thread.meta?.cwd).padEnd(20);
|
|
70
|
+
const created = formatCreatedAt(thread.meta?.createdAt).padEnd(11);
|
|
71
|
+
const originator = (thread.meta?.originator ?? '-').padEnd(16);
|
|
72
|
+
lines.push(` ${name} ${dir} ${created} ${originator} ${thread.id.slice(0, 8)} ${thread.status}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Broker-owned message envelope (tickets 003/004): structural provenance
|
|
3
|
+
* framing plus an inline reply tutorial, so the protocol teaches itself
|
|
4
|
+
* without depending on prompt wording (low-invasion constraint).
|
|
5
|
+
*/
|
|
6
|
+
/** Claude trims very long from-names; keep ours within 64 codepoints. */
|
|
7
|
+
export const FROM_NAME_MAX_CODEPOINTS = 64;
|
|
8
|
+
/**
|
|
9
|
+
* Tags the envelope protocol owns. Occurrences in the body are rewritten with
|
|
10
|
+
* a `<\` prefix (embassy-style) so a body cannot forge envelope framing.
|
|
11
|
+
* `multichat-*` stays reserved so bodies quoting envelopes from legacy
|
|
12
|
+
* sessions (pre-rename) are neutralized just the same.
|
|
13
|
+
*/
|
|
14
|
+
const RESERVED_TAG_PATTERN = /<(?=\/?(?:cross-session-message|multichat-[a-z0-9-]*|crosschat-[a-z0-9-]*)(?:[>\s/]|$))/giu;
|
|
15
|
+
function escapeAttr(value) {
|
|
16
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
17
|
+
}
|
|
18
|
+
export function neutralizeReservedTags(body) {
|
|
19
|
+
return body.replace(RESERVED_TAG_PATTERN, '<\\');
|
|
20
|
+
}
|
|
21
|
+
export function composeEnvelope(input) {
|
|
22
|
+
const codepoints = [...input.fromName];
|
|
23
|
+
const shortened = codepoints.length > FROM_NAME_MAX_CODEPOINTS;
|
|
24
|
+
const fromName = shortened ? codepoints.slice(0, FROM_NAME_MAX_CODEPOINTS).join('') : input.fromName;
|
|
25
|
+
const aliasAttr = shortened ? ` from-alias="${escapeAttr(input.fromName)}"` : '';
|
|
26
|
+
const body = neutralizeReservedTags(input.body);
|
|
27
|
+
return (`<cross-session-message from-name="${escapeAttr(fromName)}" turn="${input.turn}"${aliasAttr}>\n` +
|
|
28
|
+
`<crosschat-reply-hint conversation="${input.ref}" reply-as="${escapeAttr(input.toName)}">` +
|
|
29
|
+
`回复请运行: crosschat send --conversation ${input.ref} --body "<你的回复>"</crosschat-reply-hint>\n` +
|
|
30
|
+
`新话题: crosschat send --to <名字> --body "...";超 16KiB 请写文件后只发路径\n` +
|
|
31
|
+
`${body}\n` +
|
|
32
|
+
`</cross-session-message>`);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,4FAA4F,CAAC;AAc1H,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAoB;IAClD,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,GAAG,wBAAwB,CAAC;IAC/D,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;IACrG,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,gBAAgB,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,MAAM,IAAI,GAAG,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CACL,qCAAqC,UAAU,CAAC,QAAQ,CAAC,WAAW,KAAK,CAAC,IAAI,IAAI,SAAS,KAAK;QAChG,uCAAuC,KAAK,CAAC,GAAG,eAAe,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI;QAC3F,wCAAwC,KAAK,CAAC,GAAG,2CAA2C;QAC5F,gEAAgE;QAChE,GAAG,IAAI,IAAI;QACX,0BAA0B,CAC3B,CAAC;AACJ,CAAC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Machine-readable error shared by all crosschat modules. */
|
|
2
|
+
export class MultichatError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
/** Bounded stderr tail of a spawned child process, when one failed. */
|
|
5
|
+
stderrText;
|
|
6
|
+
constructor(code, message, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.name = 'MultichatError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
if (options?.stderrText !== undefined && options.stderrText !== '') {
|
|
11
|
+
this.stderrText = options.stderrText;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAS;IACtB,uEAAuE;IACvE,UAAU,CAAU;IAEpB,YAAY,IAAY,EAAE,OAAe,EAAE,OAAkD;QAC3F,KAAK,CAAC,OAAO,EAAE,OAAuB,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,OAAO,EAAE,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,KAAK,EAAE,EAAE,CAAC;YACnE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACvC,CAAC;IACH,CAAC;CACF"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MultichatError } from './errors.js';
|
|
2
|
+
/** Canonical key used for rate-limit buckets ("claude:<id>" / "codex:<id>" / "human"). */
|
|
3
|
+
export function identityKey(identity) {
|
|
4
|
+
return identity.p === 'human' || identity.id === undefined ? identity.p : `${identity.p}:${identity.id}`;
|
|
5
|
+
}
|
|
6
|
+
export function endpointOfIdentity(identity) {
|
|
7
|
+
return identity.p === 'human' ? { p: 'human' } : { p: identity.p, id: identity.id };
|
|
8
|
+
}
|
|
9
|
+
export function identityMatchesEndpoint(identity, endpoint) {
|
|
10
|
+
if (endpoint.p === 'human')
|
|
11
|
+
return identity.p === 'human';
|
|
12
|
+
if (identity.p === 'human')
|
|
13
|
+
return false;
|
|
14
|
+
return identity.p === endpoint.p && identity.id === endpoint.id;
|
|
15
|
+
}
|
|
16
|
+
export function resolveCallerIdentity(env, scan) {
|
|
17
|
+
const socket = env.CLAUDE_CODE_MESSAGING_SOCKET ?? '';
|
|
18
|
+
const claude = socket !== '' ? claudeIdentity(socket, scan) : undefined;
|
|
19
|
+
const codexId = env.CODEX_THREAD_ID || env.CODEX_SESSION_ID || '';
|
|
20
|
+
if (claude !== undefined && codexId !== '') {
|
|
21
|
+
throw new MultichatError('CALLER_IDENTITY_CONFLICT', `Caller identity conflict: both CLAUDE_CODE_* and CODEX_* identity variables are present (socket ${socket}, thread ${codexId}). ` +
|
|
22
|
+
`自纠(临时): 命令前缀 env -u CLAUDE_CODE_MESSAGING_SOCKET -u CLAUDE_CODE_MESSAGING_TOKEN -u CLAUDE_CODE_SESSION_ID ` +
|
|
23
|
+
`(PowerShell 先 Remove-Item Env:CLAUDE_CODE_*)。` +
|
|
24
|
+
`根治: 从干净终端重启 codex daemon(codex app-server daemon stop && codex app-server daemon start)` +
|
|
25
|
+
`——daemon 会把启动时的环境传给它派生的所有 shell。`);
|
|
26
|
+
}
|
|
27
|
+
if (claude !== undefined)
|
|
28
|
+
return claude;
|
|
29
|
+
if (codexId !== '')
|
|
30
|
+
return { p: 'codex', id: codexId };
|
|
31
|
+
return { p: 'human' };
|
|
32
|
+
}
|
|
33
|
+
function claudeIdentity(socket, scan) {
|
|
34
|
+
const matches = scan.sessions.filter((session) => session.messagingSocketPath === socket && session.sessionId !== '');
|
|
35
|
+
const ids = new Set(matches.map((session) => session.sessionId));
|
|
36
|
+
if (ids.size !== 1) {
|
|
37
|
+
throw new MultichatError('IDENTITY_UNRESOLVED', `CLAUDE_CODE_MESSAGING_SOCKET does not match exactly one routable Claude session (matched ${matches.length}); cannot determine the sender.`);
|
|
38
|
+
}
|
|
39
|
+
const session = matches[0];
|
|
40
|
+
return { p: 'claude', id: session.sessionId, name: session.name };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAgB7C,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CAAC,QAAoC;IAC9D,OAAO,QAAQ,CAAC,CAAC,KAAK,OAAO,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;AAC3G,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAwB;IACzD,OAAO,QAAQ,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAAwB,EAAE,QAAqB;IACrF,IAAI,QAAQ,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC,CAAC,KAAK,OAAO,CAAC;IAC1D,IAAI,QAAQ,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,QAAQ,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,GAAuC,EACvC,IAAwB;IAExB,MAAM,MAAM,GAAG,GAAG,CAAC,4BAA4B,IAAI,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxE,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,IAAI,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC;IAClE,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,cAAc,CACtB,0BAA0B,EAC1B,mGAAmG,MAAM,YAAY,OAAO,KAAK;YAC/H,4GAA4G;YAC5G,+CAA+C;YAC/C,yFAAyF;YACzF,kCAAkC,CACrC,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;IACvD,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,IAAwB;IAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAClC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,KAAK,MAAM,IAAI,OAAO,CAAC,SAAS,KAAK,EAAE,CAChF,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACjE,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,cAAc,CACtB,qBAAqB,EACrB,4FAA4F,OAAO,CAAC,MAAM,iCAAiC,CAC5I,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC"}
|
package/dist/outbox.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { MultichatError } from './errors.js';
|
|
6
|
+
import { checkAndRecord, rateKey } from './rate-limit.js';
|
|
7
|
+
/**
|
|
8
|
+
* Opportunistic outbox (B10): when a codex delivery fails with
|
|
9
|
+
* CODEX_THREAD_BUSY_TIMEOUT / CODEX_THREAD_LOCKED, the fully composed envelope
|
|
10
|
+
* is parked on disk (one JSON file per thread under
|
|
11
|
+
* %LOCALAPPDATA%/crosschat/outbox/) and re-delivered verbatim by the next
|
|
12
|
+
* send/status invocation. No daemon; drains are short-wait and never block
|
|
13
|
+
* the invoking command for long.
|
|
14
|
+
*/
|
|
15
|
+
export const OUTBOX_MAX_PER_THREAD = 20;
|
|
16
|
+
/** Short wait for drains: opportunistic relief must not block the real command. */
|
|
17
|
+
const DEFAULT_DRAIN_BUSY_TIMEOUT_MS = 5_000;
|
|
18
|
+
/** B11: a drain round re-delivers at most this many items (rest waits for the next round). */
|
|
19
|
+
const DEFAULT_DRAIN_MAX_ITEMS = 5;
|
|
20
|
+
/** B11: wall-clock budget for one drain round, judged on the injected clock. */
|
|
21
|
+
const DEFAULT_DRAIN_BUDGET_MS = 15_000;
|
|
22
|
+
export function defaultOutboxDir() {
|
|
23
|
+
return join(process.env.LOCALAPPDATA ?? homedir(), 'crosschat', 'outbox');
|
|
24
|
+
}
|
|
25
|
+
/** Append one parked item (FIFO); refuses honestly past the per-thread cap. */
|
|
26
|
+
export function park(dir, threadId, item, nowMs = Date.now()) {
|
|
27
|
+
const items = readItems(dir, threadId) ?? []; // missing/corrupt: self-heal as empty
|
|
28
|
+
if (items.length >= OUTBOX_MAX_PER_THREAD) {
|
|
29
|
+
throw new MultichatError('OUTBOX_FULL', `Outbox full for codex thread ${threadId} (${OUTBOX_MAX_PER_THREAD} parked). ` +
|
|
30
|
+
`线程疑似已死:改投其它线程,或人工清理 ${threadFile(dir, threadId)}。`);
|
|
31
|
+
}
|
|
32
|
+
const parked = { ...item, parkedAt: nowMs, attempts: 0 };
|
|
33
|
+
writeItems(dir, threadId, [...items, parked]);
|
|
34
|
+
return parked;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Try parked items once, in FIFO order, under a per-round budget. Success
|
|
38
|
+
* removes the item; a still busy/locked thread keeps it with attempts+1;
|
|
39
|
+
* CODEX_THREAD_NOT_FOUND dead-letters the whole thread (deleted threads are
|
|
40
|
+
* unreachable forever); any other error keeps the item for a later drain.
|
|
41
|
+
* Never throws (corrupt files are skipped, a missing dir is a no-op), so it
|
|
42
|
+
* can run before any command without failing it.
|
|
43
|
+
*/
|
|
44
|
+
export async function drain(dir, deliverFn, opts = {}) {
|
|
45
|
+
const busyTimeoutMs = opts.busyTimeoutMs ?? DEFAULT_DRAIN_BUSY_TIMEOUT_MS;
|
|
46
|
+
const maxItems = opts.maxItems ?? DEFAULT_DRAIN_MAX_ITEMS;
|
|
47
|
+
const budgetMs = opts.budgetMs ?? DEFAULT_DRAIN_BUDGET_MS;
|
|
48
|
+
const now = opts.now ?? Date.now;
|
|
49
|
+
const deadline = now() + budgetMs;
|
|
50
|
+
let allowance = maxItems;
|
|
51
|
+
let files;
|
|
52
|
+
try {
|
|
53
|
+
files = readdirSync(dir);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return []; // no outbox yet: nothing parked anywhere
|
|
57
|
+
}
|
|
58
|
+
const results = [];
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
if (!file.endsWith('.json'))
|
|
61
|
+
continue;
|
|
62
|
+
const threadId = file.slice(0, -'.json'.length);
|
|
63
|
+
const items = readItems(dir, threadId);
|
|
64
|
+
if (items === undefined)
|
|
65
|
+
continue; // missing/corrupt: leave the file alone
|
|
66
|
+
const remaining = [];
|
|
67
|
+
let delivered = 0;
|
|
68
|
+
let dropped = 0;
|
|
69
|
+
let deliveredName = '';
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
if (allowance <= 0 || now() >= deadline) {
|
|
72
|
+
remaining.push(item); // round budget exhausted: keep verbatim for the next drain
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (opts.rateDir !== undefined) {
|
|
76
|
+
try {
|
|
77
|
+
checkAndRecord(opts.rateDir, rateKey(item.callerKey ?? 'human', `codex:${threadId}`), now());
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof MultichatError && err.code === 'RATE_LIMITED') {
|
|
81
|
+
remaining.push(item); // window full: skip this round, retry next drain
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
allowance -= 1;
|
|
88
|
+
try {
|
|
89
|
+
await deliverFn(threadId, item.envelope, busyTimeoutMs);
|
|
90
|
+
delivered++;
|
|
91
|
+
deliveredName = item.toName;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err instanceof MultichatError && err.code === 'CODEX_THREAD_NOT_FOUND') {
|
|
95
|
+
// Dead letter: the thread is gone, so every parked item for it is
|
|
96
|
+
// unreachable — discard all of them, not just this one.
|
|
97
|
+
dropped = items.length - delivered;
|
|
98
|
+
remaining.length = 0;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
if (err instanceof MultichatError &&
|
|
102
|
+
(err.code === 'CODEX_THREAD_BUSY_TIMEOUT' || err.code === 'CODEX_THREAD_LOCKED')) {
|
|
103
|
+
// ponytail: attempts grows unbounded; cap/age-out if a stuck thread ever floods the file.
|
|
104
|
+
remaining.push({ ...item, attempts: item.attempts + 1 });
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
remaining.push(item); // unknown state: keep, try again next drain
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const changed = remaining.length !== items.length || remaining.some((item, i) => item !== items[i]);
|
|
112
|
+
if (changed)
|
|
113
|
+
writeItems(dir, threadId, remaining);
|
|
114
|
+
if (delivered > 0 || remaining.length > 0 || dropped > 0) {
|
|
115
|
+
results.push({
|
|
116
|
+
threadId,
|
|
117
|
+
toName: deliveredName || items.at(-1)?.toName || threadId,
|
|
118
|
+
delivered,
|
|
119
|
+
remaining: remaining.length,
|
|
120
|
+
dropped,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
/** Parked item count per thread (doctor display); corrupt/empty files are skipped. */
|
|
127
|
+
export function outboxSummary(dir) {
|
|
128
|
+
let files;
|
|
129
|
+
try {
|
|
130
|
+
files = readdirSync(dir);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const summary = [];
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
if (!file.endsWith('.json'))
|
|
138
|
+
continue;
|
|
139
|
+
const threadId = file.slice(0, -'.json'.length);
|
|
140
|
+
const items = readItems(dir, threadId);
|
|
141
|
+
if (items !== undefined && items.length > 0)
|
|
142
|
+
summary.push({ threadId, count: items.length });
|
|
143
|
+
}
|
|
144
|
+
return summary;
|
|
145
|
+
}
|
|
146
|
+
function threadFile(dir, threadId) {
|
|
147
|
+
return join(dir, `${threadId}.json`);
|
|
148
|
+
}
|
|
149
|
+
function readItems(dir, threadId) {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(readFileSync(threadFile(dir, threadId), 'utf8'));
|
|
152
|
+
return Array.isArray(parsed.items) ? parsed.items : undefined;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Atomic write (tmp+rename); an emptied outbox deletes its file. */
|
|
159
|
+
function writeItems(dir, threadId, items) {
|
|
160
|
+
const file = threadFile(dir, threadId);
|
|
161
|
+
if (items.length === 0) {
|
|
162
|
+
rmSync(file, { force: true });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
mkdirSync(dir, { recursive: true });
|
|
166
|
+
// ponytail: no cross-process locking; two concurrent drains can double-deliver
|
|
167
|
+
// one item — rare and downstream-idempotent enough; add a lockfile if it bites.
|
|
168
|
+
const tmp = join(dir, `${threadId}.${process.pid}.${Date.now()}.tmp`);
|
|
169
|
+
writeFileSync(tmp, JSON.stringify({ items }), 'utf8');
|
|
170
|
+
renameSync(tmp, file);
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../src/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createConnection } from 'node:net';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { MultichatError } from '../errors.js';
|
|
4
|
+
/** Windows named pipe path prefix used by Claude Code messaging sockets. */
|
|
5
|
+
export const PIPE_PREFIX = '\\\\.\\pipe\\';
|
|
6
|
+
export class WindowsNamedPipeTransport {
|
|
7
|
+
async sendLines(pipePath, lines) {
|
|
8
|
+
await new Promise((resolve, reject) => {
|
|
9
|
+
let settled = false;
|
|
10
|
+
let connected = false;
|
|
11
|
+
let ended = false;
|
|
12
|
+
let lastError;
|
|
13
|
+
let flushTimer;
|
|
14
|
+
const settle = (outcome) => {
|
|
15
|
+
if (settled)
|
|
16
|
+
return;
|
|
17
|
+
settled = true;
|
|
18
|
+
if (flushTimer !== undefined)
|
|
19
|
+
clearTimeout(flushTimer);
|
|
20
|
+
outcome();
|
|
21
|
+
};
|
|
22
|
+
// The peer normally closes the connection right after our EOF; a short
|
|
23
|
+
// grace period lets a broken pipe surface as an error (named pipes can
|
|
24
|
+
// report the write error a tick AFTER the end callback) instead of a
|
|
25
|
+
// false "delivered" when the peer just holds the connection open.
|
|
26
|
+
const FLUSH_GRACE_MS = 250;
|
|
27
|
+
const socket = createConnection({ path: pipePath }, () => {
|
|
28
|
+
connected = true;
|
|
29
|
+
try {
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
socket.write(line.endsWith('\n') ? line : `${line}\n`);
|
|
32
|
+
}
|
|
33
|
+
socket.end(() => {
|
|
34
|
+
ended = true;
|
|
35
|
+
flushTimer = setTimeout(() => settle(resolve), FLUSH_GRACE_MS);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
lastError = err;
|
|
40
|
+
socket.destroy();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
socket.on('error', (err) => {
|
|
44
|
+
lastError = err;
|
|
45
|
+
});
|
|
46
|
+
socket.on('close', (hadError) => {
|
|
47
|
+
if (hadError || lastError !== undefined || !ended) {
|
|
48
|
+
if (!connected) {
|
|
49
|
+
settle(() => reject(new MultichatError('PIPE_CONNECT_FAILED', `Cannot connect to pipe ${pipePath}.`, {
|
|
50
|
+
cause: lastError,
|
|
51
|
+
})));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
settle(() => reject(new MultichatError('PIPE_WRITE_UNCERTAIN', `Connection to pipe ${pipePath} failed or closed early; delivery state unknown.`, { cause: lastError })));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
settle(resolve);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Phase 2: Unix domain sockets. */
|
|
65
|
+
export class PosixPipeTransport {
|
|
66
|
+
async sendLines() {
|
|
67
|
+
throw new MultichatError('NOT_IMPLEMENTED', 'POSIX pipe transport is phase 2.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function getPipeTransport() {
|
|
71
|
+
return process.platform === 'win32' ? new WindowsNamedPipeTransport() : new PosixPipeTransport();
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=pipe-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipe-transport.js","sourceRoot":"","sources":["../../src/platform/pipe-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
/** True when a process with this pid exists (signal 0 probe). */
|
|
3
|
+
export function isProcessAlive(pid) {
|
|
4
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
5
|
+
return false;
|
|
6
|
+
try {
|
|
7
|
+
process.kill(pid, 0);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
// EPERM means the process exists but we may not signal it.
|
|
12
|
+
return err.code === 'EPERM';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=process-inspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-inspector.js","sourceRoot":"","sources":["../../src/platform/process-inspector.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,2DAA2D;QAC3D,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC"}
|