codex-to-im 1.0.38 → 1.0.40
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/README.md +9 -0
- package/README_EN.md +9 -0
- package/dist/daemon.mjs +101 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
3. 把桌面里的 Codex 会话接到 IM
|
|
12
12
|
4. 在 IM 中继续对话、切线程、查看状态
|
|
13
13
|
|
|
14
|
+
## 项目来源
|
|
15
|
+
|
|
16
|
+
当前代码库是两个早期工程整合后的延续版本:
|
|
17
|
+
|
|
18
|
+
- `Claude-to-IM`
|
|
19
|
+
- `Claude-to-IM-skill`
|
|
20
|
+
|
|
21
|
+
`codex-to-im` 基于这两个项目继续演进,重构为单包本地应用,并统一了本地工作台、bridge、共享线程工作流,以及可选的 skill 集成方式。
|
|
22
|
+
|
|
14
23
|
## 核心能力
|
|
15
24
|
|
|
16
25
|
- 共享桌面线程:把 Codex Desktop 中正在使用的 thread 绑定到 IM,在 IM 中继续同一条会话。
|
package/README_EN.md
CHANGED
|
@@ -11,6 +11,15 @@ Its main path is not to modify Codex itself, but to:
|
|
|
11
11
|
3. bind desktop Codex sessions to IM chats
|
|
12
12
|
4. continue the same conversation, switch threads, and inspect status from IM
|
|
13
13
|
|
|
14
|
+
## Project Origin
|
|
15
|
+
|
|
16
|
+
The current codebase is a consolidated continuation of two earlier projects:
|
|
17
|
+
|
|
18
|
+
- `Claude-to-IM`
|
|
19
|
+
- `Claude-to-IM-skill`
|
|
20
|
+
|
|
21
|
+
`codex-to-im` continues from those two repositories and has been reworked into a single local package with a unified workbench, bridge, shared-thread workflow, and optional skill integration model.
|
|
22
|
+
|
|
14
23
|
## Core Capabilities
|
|
15
24
|
|
|
16
25
|
- Shared desktop threads: bind a thread currently used in Codex Desktop to IM and continue the same conversation there.
|
package/dist/daemon.mjs
CHANGED
|
@@ -4059,6 +4059,7 @@ var init_codex_provider = __esm({
|
|
|
4059
4059
|
thread = codex.startThread(threadOptions);
|
|
4060
4060
|
}
|
|
4061
4061
|
let sawAnyEvent = false;
|
|
4062
|
+
let sawTerminalEvent = false;
|
|
4062
4063
|
try {
|
|
4063
4064
|
const { events } = await thread.runStreamed(input, {
|
|
4064
4065
|
signal: params.abortController?.signal
|
|
@@ -4093,19 +4094,25 @@ var init_codex_provider = __esm({
|
|
|
4093
4094
|
} : void 0,
|
|
4094
4095
|
...threadId ? { session_id: threadId } : {}
|
|
4095
4096
|
}));
|
|
4097
|
+
sawTerminalEvent = true;
|
|
4096
4098
|
break;
|
|
4097
4099
|
}
|
|
4098
4100
|
case "turn.failed": {
|
|
4099
4101
|
const error = event.message;
|
|
4100
4102
|
controller.enqueue(sseEvent("error", error || "Turn failed"));
|
|
4103
|
+
sawTerminalEvent = true;
|
|
4101
4104
|
break;
|
|
4102
4105
|
}
|
|
4103
4106
|
case "error": {
|
|
4104
4107
|
const error = event.message;
|
|
4105
4108
|
controller.enqueue(sseEvent("error", error || "Thread error"));
|
|
4109
|
+
sawTerminalEvent = true;
|
|
4106
4110
|
break;
|
|
4107
4111
|
}
|
|
4108
4112
|
}
|
|
4113
|
+
if (sawTerminalEvent) {
|
|
4114
|
+
break;
|
|
4115
|
+
}
|
|
4109
4116
|
}
|
|
4110
4117
|
break;
|
|
4111
4118
|
} catch (err) {
|
|
@@ -16621,6 +16628,8 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
|
16621
16628
|
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
16622
16629
|
startedAt: safeTimestamp,
|
|
16623
16630
|
lastActivityAt: safeTimestamp,
|
|
16631
|
+
lastStatusText: null,
|
|
16632
|
+
lastStatusAt: 0,
|
|
16624
16633
|
userText: null,
|
|
16625
16634
|
lastAssistantText: null,
|
|
16626
16635
|
lastCommentaryText: null,
|
|
@@ -19318,6 +19327,14 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19318
19327
|
}
|
|
19319
19328
|
|
|
19320
19329
|
// src/lib/bridge/interactive-runtime.ts
|
|
19330
|
+
var TERMINAL_SESSION_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
19331
|
+
"completed",
|
|
19332
|
+
"failed",
|
|
19333
|
+
"aborted"
|
|
19334
|
+
]);
|
|
19335
|
+
function isTerminalSessionHealthStatus(status) {
|
|
19336
|
+
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
19337
|
+
}
|
|
19321
19338
|
function buildInteractiveIdleReminderNotice() {
|
|
19322
19339
|
return [
|
|
19323
19340
|
"\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
|
|
@@ -19391,6 +19408,24 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
19391
19408
|
await remindIdleInteractiveTask(task);
|
|
19392
19409
|
}
|
|
19393
19410
|
}
|
|
19411
|
+
function reconcileTerminalSessionRuntimeState() {
|
|
19412
|
+
const store = deps.getStore();
|
|
19413
|
+
for (const session of store.listSessions()) {
|
|
19414
|
+
const queuedCount = getQueuedCount(session.id);
|
|
19415
|
+
const persistedQueuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
|
|
19416
|
+
const hasActiveTask = getState2().activeTasks.has(session.id);
|
|
19417
|
+
if (hasActiveTask || queuedCount > 0) continue;
|
|
19418
|
+
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
19419
|
+
if (persistedQueuedCount === 0 && session.runtime_status !== "running" && session.runtime_status !== "queued") {
|
|
19420
|
+
continue;
|
|
19421
|
+
}
|
|
19422
|
+
store.updateSession(session.id, {
|
|
19423
|
+
queued_count: 0,
|
|
19424
|
+
runtime_status: "idle",
|
|
19425
|
+
last_runtime_update_at: deps.nowIso()
|
|
19426
|
+
});
|
|
19427
|
+
}
|
|
19428
|
+
}
|
|
19394
19429
|
function resetPersistedInteractiveRuntimeState() {
|
|
19395
19430
|
const store = deps.getStore();
|
|
19396
19431
|
for (const session of store.listSessions()) {
|
|
@@ -19452,6 +19487,7 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
19452
19487
|
releaseInteractiveTask,
|
|
19453
19488
|
syncSessionRuntimeState,
|
|
19454
19489
|
reconcileIdleInteractiveTasks,
|
|
19490
|
+
reconcileTerminalSessionRuntimeState,
|
|
19455
19491
|
resetPersistedInteractiveRuntimeState,
|
|
19456
19492
|
processWithSessionLock
|
|
19457
19493
|
};
|
|
@@ -20601,6 +20637,8 @@ var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
|
20601
20637
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
20602
20638
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
20603
20639
|
var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
|
|
20640
|
+
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
20641
|
+
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
20604
20642
|
var MIRROR_IDLE_TIMEOUT_MS = 6e5;
|
|
20605
20643
|
function describeUnknownError(error) {
|
|
20606
20644
|
if (error instanceof Error) {
|
|
@@ -20782,6 +20820,21 @@ function getMirrorStreamingText(subscription, turnState) {
|
|
|
20782
20820
|
);
|
|
20783
20821
|
return rendered || buildMirrorTitle(title, markdown);
|
|
20784
20822
|
}
|
|
20823
|
+
function getMirrorStructuredStreamStatusConfig() {
|
|
20824
|
+
const { store } = getBridgeContext();
|
|
20825
|
+
const idleStartSeconds = parseInt(store.getSetting("bridge_stream_status_idle_start_seconds") || "", 10);
|
|
20826
|
+
const heartbeatSeconds = parseInt(store.getSetting("bridge_stream_status_check_interval_seconds") || "", 10);
|
|
20827
|
+
return {
|
|
20828
|
+
idleStartMs: Math.max(
|
|
20829
|
+
0,
|
|
20830
|
+
(Number.isFinite(idleStartSeconds) && idleStartSeconds > 0 ? idleStartSeconds : MIRROR_STREAM_STATUS_IDLE_START_MS / 1e3) * 1e3
|
|
20831
|
+
),
|
|
20832
|
+
heartbeatMs: Math.max(
|
|
20833
|
+
1e3,
|
|
20834
|
+
(Number.isFinite(heartbeatSeconds) && heartbeatSeconds > 0 ? heartbeatSeconds : MIRROR_STREAM_STATUS_HEARTBEAT_MS / 1e3) * 1e3
|
|
20835
|
+
)
|
|
20836
|
+
};
|
|
20837
|
+
}
|
|
20785
20838
|
function startMirrorStreaming(subscription, turnState) {
|
|
20786
20839
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
20787
20840
|
if (!adapter || turnState.streamStarted) return;
|
|
@@ -20805,6 +20858,50 @@ function createMirrorStreamFeedbackTarget(subscription, turnState, adapter) {
|
|
|
20805
20858
|
}
|
|
20806
20859
|
};
|
|
20807
20860
|
}
|
|
20861
|
+
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
20862
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
20863
|
+
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
20864
|
+
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
20865
|
+
const startedAtMs = Date.parse(turnState.startedAt);
|
|
20866
|
+
if (!Number.isFinite(startedAtMs)) return;
|
|
20867
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
20868
|
+
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
20869
|
+
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
20870
|
+
return;
|
|
20871
|
+
}
|
|
20872
|
+
const statusText = formatInteractiveRuntimeStatus(
|
|
20873
|
+
Math.max(0, nowMs - startedAtMs),
|
|
20874
|
+
options.silentMs
|
|
20875
|
+
);
|
|
20876
|
+
if (turnState.lastStatusText === statusText) return;
|
|
20877
|
+
pushStreamFeedbackStatus(
|
|
20878
|
+
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
20879
|
+
statusText
|
|
20880
|
+
);
|
|
20881
|
+
turnState.lastStatusText = statusText;
|
|
20882
|
+
turnState.lastStatusAt = nowMs;
|
|
20883
|
+
}
|
|
20884
|
+
function refreshMirrorStreamingStatus(subscription, nowMs = Date.now(), config2 = getMirrorStructuredStreamStatusConfig()) {
|
|
20885
|
+
const pendingTurn = subscription.pendingTurn;
|
|
20886
|
+
if (!pendingTurn?.streamStarted) return;
|
|
20887
|
+
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
20888
|
+
const lastActivityMs = Date.parse(pendingTurn.lastActivityAt);
|
|
20889
|
+
if (!Number.isFinite(startedAtMs) || !Number.isFinite(lastActivityMs)) return;
|
|
20890
|
+
const elapsedMs = nowMs - startedAtMs;
|
|
20891
|
+
if (elapsedMs < config2.idleStartMs) return;
|
|
20892
|
+
const silentMs = nowMs - lastActivityMs;
|
|
20893
|
+
if (silentMs < config2.heartbeatMs) return;
|
|
20894
|
+
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
20895
|
+
nowMs,
|
|
20896
|
+
silentMs,
|
|
20897
|
+
minIntervalMs: config2.heartbeatMs
|
|
20898
|
+
});
|
|
20899
|
+
}
|
|
20900
|
+
function refreshActiveMirrorStreamingStatuses(nowMs = Date.now()) {
|
|
20901
|
+
for (const subscription of getState().mirrorSubscriptions.values()) {
|
|
20902
|
+
refreshMirrorStreamingStatus(subscription, nowMs);
|
|
20903
|
+
}
|
|
20904
|
+
}
|
|
20808
20905
|
function updateMirrorStreaming(subscription, turnState) {
|
|
20809
20906
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
20810
20907
|
if (!adapter) return;
|
|
@@ -20812,6 +20909,7 @@ function updateMirrorStreaming(subscription, turnState) {
|
|
|
20812
20909
|
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
20813
20910
|
getMirrorStreamingText(subscription, turnState)
|
|
20814
20911
|
);
|
|
20912
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
20815
20913
|
}
|
|
20816
20914
|
function updateMirrorToolProgress(subscription, turnState) {
|
|
20817
20915
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
@@ -20820,6 +20918,7 @@ function updateMirrorToolProgress(subscription, turnState) {
|
|
|
20820
20918
|
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
20821
20919
|
Array.from(turnState.toolCalls.values())
|
|
20822
20920
|
);
|
|
20921
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
20823
20922
|
}
|
|
20824
20923
|
function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
20825
20924
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
@@ -20928,6 +21027,7 @@ function resetMirrorSessionForInteractiveRun(sessionId) {
|
|
|
20928
21027
|
}
|
|
20929
21028
|
async function reconcileMirrorSubscriptions() {
|
|
20930
21029
|
await MIRROR_RUNTIME.reconcileMirrorSubscriptions();
|
|
21030
|
+
refreshActiveMirrorStreamingStatuses();
|
|
20931
21031
|
}
|
|
20932
21032
|
function clearMirrorSubscriptions() {
|
|
20933
21033
|
MIRROR_RUNTIME.clearMirrorSubscriptions();
|
|
@@ -20978,6 +21078,7 @@ async function start() {
|
|
|
20978
21078
|
});
|
|
20979
21079
|
try {
|
|
20980
21080
|
SESSION_HEALTH_RUNTIME.reconcileSessionHealth();
|
|
21081
|
+
INTERACTIVE_RUNTIME.reconcileTerminalSessionRuntimeState();
|
|
20981
21082
|
} catch (err) {
|
|
20982
21083
|
console.error("[bridge-manager] Session health reconcile failed:", describeUnknownError(err));
|
|
20983
21084
|
}
|
package/package.json
CHANGED