lark-coding-assistant 0.2.6 → 0.2.8
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/dist/cli.js +52 -1
- package/dist/cli.js.map +1 -1
- package/dist/daemon-entry.js +106 -36
- package/dist/daemon-entry.js.map +1 -1
- package/dist/hook-entry.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon-entry.js
CHANGED
|
@@ -450,6 +450,7 @@ var SessionReconciler = class {
|
|
|
450
450
|
misses = /* @__PURE__ */ new Map();
|
|
451
451
|
async reconcile(input, discover = false, signal) {
|
|
452
452
|
const sessions = { ...input.sessions };
|
|
453
|
+
const removedSessions = [];
|
|
453
454
|
let changed = false;
|
|
454
455
|
for (const [id, session] of Object.entries(sessions)) {
|
|
455
456
|
if (signal?.aborted) throw signal.reason;
|
|
@@ -467,9 +468,14 @@ var SessionReconciler = class {
|
|
|
467
468
|
continue;
|
|
468
469
|
}
|
|
469
470
|
if (result2.status === "dead") {
|
|
471
|
+
const terminalOutput = await this.tmux.capture(result2.pane.paneId, 80, signal).catch((error) => {
|
|
472
|
+
void this.log(`failed to capture dead tmux pane ${result2.pane.paneId}: ${errorMessage2(error)}`);
|
|
473
|
+
return "";
|
|
474
|
+
});
|
|
470
475
|
await this.tmux.killSession(result2.pane.sessionName, signal).catch((error) => this.log(
|
|
471
476
|
`failed to clean dead tmux session ${result2.pane.sessionName}: ${errorMessage2(error)}`
|
|
472
477
|
));
|
|
478
|
+
removedSessions.push({ session, pane: result2.pane, terminalOutput });
|
|
473
479
|
delete sessions[id];
|
|
474
480
|
this.misses.delete(id);
|
|
475
481
|
changed = true;
|
|
@@ -478,6 +484,7 @@ var SessionReconciler = class {
|
|
|
478
484
|
const misses = (this.misses.get(id) ?? 0) + 1;
|
|
479
485
|
this.misses.set(id, misses);
|
|
480
486
|
if (misses < this.missingThreshold) continue;
|
|
487
|
+
removedSessions.push({ session });
|
|
481
488
|
delete sessions[id];
|
|
482
489
|
this.misses.delete(id);
|
|
483
490
|
changed = true;
|
|
@@ -489,7 +496,7 @@ var SessionReconciler = class {
|
|
|
489
496
|
if (activeSessionId !== input.activeSessionId) changed = true;
|
|
490
497
|
const removedActive = input.activeSessionId && !sessions[input.activeSessionId] ? input.sessions?.[input.activeSessionId] : void 0;
|
|
491
498
|
const state = changed ? { ...input, sessions, activeSessionId, updatedAt: Date.now() } : input;
|
|
492
|
-
return { state, liveSessions: Object.values(sessions), removedActive, changed };
|
|
499
|
+
return { state, liveSessions: Object.values(sessions), removedActive, removedSessions, changed };
|
|
493
500
|
}
|
|
494
501
|
async confirm(session, signal) {
|
|
495
502
|
const direct = await this.tmux.inspectStatus(session.paneId, signal);
|
|
@@ -3222,6 +3229,10 @@ var AssistantDaemon = class {
|
|
|
3222
3229
|
}
|
|
3223
3230
|
case "start":
|
|
3224
3231
|
return this.startSession(request.sessionId, request.cwd, request.agent, request.resume);
|
|
3232
|
+
case "reconcile": {
|
|
3233
|
+
await this.reconcileSessions(true);
|
|
3234
|
+
return { ok: true };
|
|
3235
|
+
}
|
|
3225
3236
|
case "status":
|
|
3226
3237
|
return { ok: true, value: await this.runtimeStatus(request.sessionId) };
|
|
3227
3238
|
case "tail":
|
|
@@ -3333,11 +3344,6 @@ var AssistantDaemon = class {
|
|
|
3333
3344
|
agentVersion,
|
|
3334
3345
|
updatedAt: Date.now()
|
|
3335
3346
|
};
|
|
3336
|
-
const pendingClaim = this.pendingAgentSessionClaims.get(sessionId);
|
|
3337
|
-
if (pendingClaim) {
|
|
3338
|
-
const claimed = this.claimStartingAgentSession(session, pendingClaim);
|
|
3339
|
-
if (!claimed.ok) return claimed;
|
|
3340
|
-
}
|
|
3341
3347
|
if (resume && resume.mode !== "picker") {
|
|
3342
3348
|
const initialClaim = await context.stage(
|
|
3343
3349
|
"agent-identity",
|
|
@@ -3376,11 +3382,6 @@ var AssistantDaemon = class {
|
|
|
3376
3382
|
));
|
|
3377
3383
|
await this.rememberSessionWorkspace(session.cwd);
|
|
3378
3384
|
}
|
|
3379
|
-
const lateClaim = this.pendingAgentSessionClaims.get(sessionId);
|
|
3380
|
-
if (lateClaim) {
|
|
3381
|
-
const claimed = this.claimStartingAgentSession(session, lateClaim);
|
|
3382
|
-
if (!claimed.ok) return claimed;
|
|
3383
|
-
}
|
|
3384
3385
|
try {
|
|
3385
3386
|
await context.stage("metadata", () => this.tmux.writeMetadata(pane.sessionName, {
|
|
3386
3387
|
managed: true,
|
|
@@ -3391,6 +3392,7 @@ var AssistantDaemon = class {
|
|
|
3391
3392
|
agentSessionId: session.agentSessionId
|
|
3392
3393
|
}, context.signal));
|
|
3393
3394
|
await context.stage("state", () => this.commitStartedSession(session, binding));
|
|
3395
|
+
await this.clearSessionExitEvent(sessionId, context.startedAt);
|
|
3394
3396
|
} catch (error) {
|
|
3395
3397
|
return fail(isAppError(error) ? error : new AppError("START_FAILED", "failed to persist completed session", { sessionId }, { cause: error }));
|
|
3396
3398
|
}
|
|
@@ -4480,6 +4482,26 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4480
4482
|
}
|
|
4481
4483
|
await this.store.saveState(this.state);
|
|
4482
4484
|
}
|
|
4485
|
+
async clearSessionExitEvent(sessionId, before) {
|
|
4486
|
+
const event = this.state.recentSessionExits?.[sessionId];
|
|
4487
|
+
if (!event || event.occurredAt >= before) return;
|
|
4488
|
+
const recentSessionExits = { ...this.state.recentSessionExits };
|
|
4489
|
+
delete recentSessionExits[sessionId];
|
|
4490
|
+
this.state = { ...this.state, recentSessionExits, updatedAt: Date.now() };
|
|
4491
|
+
await this.store.saveState(this.state);
|
|
4492
|
+
}
|
|
4493
|
+
async rememberSessionExitEvent(event) {
|
|
4494
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
4495
|
+
const recentSessionExits = Object.fromEntries(Object.entries(this.state.recentSessionExits ?? {}).filter(([, value]) => value.occurredAt >= cutoff));
|
|
4496
|
+
recentSessionExits[event.sessionId] = event;
|
|
4497
|
+
const retained = Object.entries(recentSessionExits).sort(([, left], [, right]) => right.occurredAt - left.occurredAt).slice(0, 20);
|
|
4498
|
+
this.state = {
|
|
4499
|
+
...this.state,
|
|
4500
|
+
recentSessionExits: Object.fromEntries(retained),
|
|
4501
|
+
updatedAt: Date.now()
|
|
4502
|
+
};
|
|
4503
|
+
await this.store.saveState(this.state);
|
|
4504
|
+
}
|
|
4483
4505
|
async resetOwner() {
|
|
4484
4506
|
this.pendingMessages.length = 0;
|
|
4485
4507
|
this.pendingInteractionInput = void 0;
|
|
@@ -4633,20 +4655,56 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4633
4655
|
if (session && session.agent !== candidate.agent) {
|
|
4634
4656
|
return { ok: false, error: "agent-session candidate does not match managed session" };
|
|
4635
4657
|
}
|
|
4636
|
-
|
|
4637
|
-
if (owner) {
|
|
4658
|
+
if (!session) {
|
|
4638
4659
|
this.pendingAgentSessionClaims.set(candidate.sessionId, candidate);
|
|
4639
4660
|
await this.log(
|
|
4640
|
-
`agent session
|
|
4661
|
+
`agent session hook candidate awaiting managed session: session=${candidate.sessionId} agent=${candidate.agent} agentSession=${candidate.agentSessionId}`
|
|
4641
4662
|
);
|
|
4642
|
-
|
|
4643
|
-
return fail(agentSessionInUse(candidate.sessionId, owner.id));
|
|
4663
|
+
return { ok: true };
|
|
4644
4664
|
}
|
|
4645
|
-
if (
|
|
4646
|
-
this.pendingAgentSessionClaims.
|
|
4665
|
+
if (session.agentSessionId) {
|
|
4666
|
+
this.pendingAgentSessionClaims.delete(session.id);
|
|
4667
|
+
if (session.agentSessionId !== candidate.agentSessionId) {
|
|
4668
|
+
await this.log(
|
|
4669
|
+
`ignored agent session hook candidate: session=${session.id} agent=${session.agent} hookSession=${candidate.agentSessionId} confirmedSession=${session.agentSessionId}`
|
|
4670
|
+
);
|
|
4671
|
+
}
|
|
4647
4672
|
return { ok: true };
|
|
4648
4673
|
}
|
|
4649
|
-
|
|
4674
|
+
this.pendingAgentSessionClaims.set(candidate.sessionId, candidate);
|
|
4675
|
+
const agentSessionId = await this.resolvePaneNativeAgentSessionId(session);
|
|
4676
|
+
if (!agentSessionId) return { ok: true };
|
|
4677
|
+
if (agentSessionId !== candidate.agentSessionId) {
|
|
4678
|
+
await this.log(
|
|
4679
|
+
`ignored stale agent session hook candidate: session=${session.id} agent=${session.agent} hookSession=${candidate.agentSessionId} pidSession=${agentSessionId}`
|
|
4680
|
+
);
|
|
4681
|
+
}
|
|
4682
|
+
const confirmed = { ...candidate, agentSessionId, source: "pid-confirmed" };
|
|
4683
|
+
const owner = this.findAgentSessionOwner(session.agent, agentSessionId, session.id);
|
|
4684
|
+
if (owner) {
|
|
4685
|
+
await this.log(
|
|
4686
|
+
`confirmed agent session conflict: session=${session.id} agent=${session.agent} agentSession=${agentSessionId} owner=${owner.id}`
|
|
4687
|
+
);
|
|
4688
|
+
await this.rejectDuplicateAgentSession(confirmed, owner);
|
|
4689
|
+
return fail(agentSessionInUse(session.id, owner.id, agentSessionId));
|
|
4690
|
+
}
|
|
4691
|
+
return this.persistConfirmedAgentSessionClaim(session, confirmed);
|
|
4692
|
+
}
|
|
4693
|
+
async resolvePaneNativeAgentSessionId(session) {
|
|
4694
|
+
let pane;
|
|
4695
|
+
try {
|
|
4696
|
+
pane = await this.tmux.inspect(session.paneId);
|
|
4697
|
+
} catch (error) {
|
|
4698
|
+
void this.log(`failed to inspect pane for native session: session=${session.id} error=${errorMessage3(error)}`);
|
|
4699
|
+
return void 0;
|
|
4700
|
+
}
|
|
4701
|
+
if (!pane || pane.dead) return void 0;
|
|
4702
|
+
return resolveNativeAgentSessionId(session.agent, pane.pid).catch((error) => {
|
|
4703
|
+
void this.log(`failed to resolve native agent session for ${session.id}: ${errorMessage3(error)}`);
|
|
4704
|
+
return void 0;
|
|
4705
|
+
});
|
|
4706
|
+
}
|
|
4707
|
+
async persistConfirmedAgentSessionClaim(session, candidate) {
|
|
4650
4708
|
const updated = { ...session, agentSessionId: candidate.agentSessionId, updatedAt: Date.now() };
|
|
4651
4709
|
this.state = {
|
|
4652
4710
|
...this.state,
|
|
@@ -4671,12 +4729,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4671
4729
|
async refreshNativeAgentSessionClaims() {
|
|
4672
4730
|
for (const session of Object.values(this.state.sessions ?? {})) {
|
|
4673
4731
|
if (session.agentSessionId) continue;
|
|
4674
|
-
const
|
|
4675
|
-
if (!pane || pane.dead) continue;
|
|
4676
|
-
const agentSessionId = await resolveNativeAgentSessionId(session.agent, pane.pid).catch((error) => {
|
|
4677
|
-
void this.log(`failed to resolve native agent session for ${session.id}: ${errorMessage3(error)}`);
|
|
4678
|
-
return void 0;
|
|
4679
|
-
});
|
|
4732
|
+
const agentSessionId = await this.resolvePaneNativeAgentSessionId(session);
|
|
4680
4733
|
if (!agentSessionId) continue;
|
|
4681
4734
|
await this.handleAgentSessionStarted({
|
|
4682
4735
|
sessionId: session.id,
|
|
@@ -4696,11 +4749,6 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4696
4749
|
session.agentSessionId = committedClaim2;
|
|
4697
4750
|
return { ok: true };
|
|
4698
4751
|
}
|
|
4699
|
-
const pending2 = this.pendingAgentSessionClaims.get(session.id);
|
|
4700
|
-
if (pending2) {
|
|
4701
|
-
const claimed = this.claimStartingAgentSession(session, pending2);
|
|
4702
|
-
if (!claimed.ok || session.agentSessionId) return claimed;
|
|
4703
|
-
}
|
|
4704
4752
|
if (signal?.aborted) throw signal.reason;
|
|
4705
4753
|
const pane2 = await this.tmux.inspect(session.paneId, signal);
|
|
4706
4754
|
if (signal?.aborted) throw signal.reason;
|
|
@@ -4725,8 +4773,6 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4725
4773
|
const pane = await this.tmux.inspect(session.paneId, signal);
|
|
4726
4774
|
if (signal?.aborted) throw signal.reason;
|
|
4727
4775
|
if (!pane || pane.dead) return fail(await this.startupExitedError(session, pane));
|
|
4728
|
-
const pending = this.pendingAgentSessionClaims.get(session.id);
|
|
4729
|
-
if (pending) return this.claimStartingAgentSession(session, pending);
|
|
4730
4776
|
const committedClaim = this.state.sessions?.[session.id]?.agentSessionId;
|
|
4731
4777
|
if (committedClaim) {
|
|
4732
4778
|
session.agentSessionId = committedClaim;
|
|
@@ -4749,7 +4795,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4749
4795
|
const owner = this.findAgentSessionOwner(candidate.agent, candidate.agentSessionId, session.id);
|
|
4750
4796
|
if (owner) {
|
|
4751
4797
|
this.pendingAgentSessionClaims.delete(session.id);
|
|
4752
|
-
return fail(agentSessionInUse(session.id, owner.id));
|
|
4798
|
+
return fail(agentSessionInUse(session.id, owner.id, candidate.agentSessionId));
|
|
4753
4799
|
}
|
|
4754
4800
|
session.agentSessionId = candidate.agentSessionId;
|
|
4755
4801
|
session.updatedAt = Date.now();
|
|
@@ -4787,6 +4833,14 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4787
4833
|
return pending ? { id: pending.sessionId } : void 0;
|
|
4788
4834
|
}
|
|
4789
4835
|
async rejectDuplicateAgentSession(candidate, owner) {
|
|
4836
|
+
await this.rememberSessionExitEvent({
|
|
4837
|
+
sessionId: candidate.sessionId,
|
|
4838
|
+
agent: candidate.agent,
|
|
4839
|
+
reason: "agent-session-conflict",
|
|
4840
|
+
agentSessionId: candidate.agentSessionId,
|
|
4841
|
+
ownerSessionId: owner.id,
|
|
4842
|
+
occurredAt: Date.now()
|
|
4843
|
+
});
|
|
4790
4844
|
const duplicate = this.state.sessions?.[candidate.sessionId];
|
|
4791
4845
|
if (duplicate) await this.stopSession(duplicate.id).catch((error) => this.log(
|
|
4792
4846
|
`failed to stop duplicate agent session ${duplicate.id}: ${errorMessage3(error)}`
|
|
@@ -4799,7 +4853,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4799
4853
|
if (this.state.boundChatId) {
|
|
4800
4854
|
await this.gateway?.sendText(
|
|
4801
4855
|
this.state.boundChatId,
|
|
4802
|
-
`${candidate.agent} \u539F\u751F session
|
|
4856
|
+
`${candidate.agent} \u539F\u751F session\u300C${candidate.agentSessionId}\u300D\u5DF2\u7531 LCA session\u300C${owner.id}\u300D\u8FDE\u63A5\uFF1B\u5DF2\u505C\u6B62\u91CD\u590D\u521B\u5EFA\u7684\u300C${candidate.sessionId}\u300D\u3002\u8BF7\u7528 /sessions \u8FDE\u63A5\u300C${owner.id}\u300D\u3002`
|
|
4803
4857
|
).catch((error) => this.log(`agent session conflict notification failed: ${errorMessage3(error)}`));
|
|
4804
4858
|
}
|
|
4805
4859
|
}
|
|
@@ -4871,6 +4925,22 @@ ${output}`
|
|
|
4871
4925
|
).catch((error) => this.log(`exit notification failed: ${errorMessage3(error)}`));
|
|
4872
4926
|
}
|
|
4873
4927
|
this.state = result2.state;
|
|
4928
|
+
for (const removed of result2.removedSessions) {
|
|
4929
|
+
const exitStatus = removed.pane?.exitStatus;
|
|
4930
|
+
if (exitStatus === void 0 || exitStatus === 0) continue;
|
|
4931
|
+
const terminalExcerpt = startupTerminalExcerpt(tailScreen(removed.terminalOutput ?? "", 80).slice(-3e3));
|
|
4932
|
+
await this.rememberSessionExitEvent({
|
|
4933
|
+
sessionId: removed.session.id,
|
|
4934
|
+
agent: removed.session.agent,
|
|
4935
|
+
reason: "agent-exited",
|
|
4936
|
+
exitStatus,
|
|
4937
|
+
terminalExcerpt: terminalExcerpt || void 0,
|
|
4938
|
+
occurredAt: Date.now()
|
|
4939
|
+
});
|
|
4940
|
+
await this.log(
|
|
4941
|
+
`agent exited: session=${removed.session.id} agent=${removed.session.agent} exit=${exitStatus}`
|
|
4942
|
+
);
|
|
4943
|
+
}
|
|
4874
4944
|
for (const sessionId of this.pendingResumePickers.keys()) {
|
|
4875
4945
|
if (!this.state.sessions?.[sessionId]) this.pendingResumePickers.delete(sessionId);
|
|
4876
4946
|
}
|
|
@@ -5035,11 +5105,11 @@ function abortableDelay(ms, signal) {
|
|
|
5035
5105
|
signal.addEventListener("abort", aborted, { once: true });
|
|
5036
5106
|
});
|
|
5037
5107
|
}
|
|
5038
|
-
function agentSessionInUse(sessionId, ownerSessionId) {
|
|
5108
|
+
function agentSessionInUse(sessionId, ownerSessionId, agentSessionId) {
|
|
5039
5109
|
return new AppError(
|
|
5040
5110
|
"AGENT_SESSION_IN_USE",
|
|
5041
5111
|
`agent session is already managed by ${ownerSessionId}`,
|
|
5042
|
-
{ sessionId, ownerSessionId }
|
|
5112
|
+
{ sessionId, ownerSessionId, agentSessionId }
|
|
5043
5113
|
);
|
|
5044
5114
|
}
|
|
5045
5115
|
function errorMessage3(error) {
|