@keyou007/agent-bot 0.1.14-alpha.1 → 0.1.14-alpha.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 +19 -1
- package/README.md +48 -15
- package/README.zh.md +48 -15
- package/config.example.yaml +1 -1
- package/dist/cli/AutostartManager.js +431 -0
- package/dist/cli/AutostartManager.js.map +1 -0
- package/dist/cli/FeishuAppConfiguration.js +21 -13
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/Initializer.js +35 -0
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js +552 -0
- package/dist/cli/SelfUpdateRunner.js.map +1 -0
- package/dist/cli/SelfUpdater.js +431 -0
- package/dist/cli/SelfUpdater.js.map +1 -0
- package/dist/cli/autostartOutput.js +63 -0
- package/dist/cli/autostartOutput.js.map +1 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +94 -18
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +14 -11
- package/dist/cli/taskGroupOptions.js.map +1 -1
- package/dist/cli/taskListOutput.js +3 -0
- package/dist/cli/taskListOutput.js.map +1 -1
- package/dist/cli/taskTarget.js +99 -0
- package/dist/cli/taskTarget.js.map +1 -0
- package/dist/cli.js +661 -58
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +8 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +16 -1
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +266 -38
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +25 -1
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +16 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +42 -6
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +136 -2
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +7 -0
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +21 -7
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +537 -13
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +53 -0
- package/dist/state/StateStore.js.map +1 -1
- package/docs/technical-reference.md +17 -7
- package/docs/technical-reference.zh.md +17 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/skills/agent-bot/SKILL.md +109 -188
- package/src/cli/AutostartManager.ts +579 -0
- package/src/cli/FeishuAppConfiguration.ts +26 -7
- package/src/cli/Initializer.ts +57 -0
- package/src/cli/SelfUpdateRunner.ts +817 -0
- package/src/cli/SelfUpdater.ts +688 -0
- package/src/cli/autostartOutput.ts +75 -0
- package/src/cli/controlProtocol.ts +119 -1
- package/src/cli/help.ts +94 -18
- package/src/cli/taskGroupOptions.ts +27 -17
- package/src/cli/taskListOutput.ts +10 -0
- package/src/cli/taskTarget.ts +168 -0
- package/src/cli.ts +809 -65
- package/src/codex/CodexRuntime.ts +8 -2
- package/src/commands/CommandRouter.ts +14 -1
- package/src/commands/commandTypes.ts +2 -0
- package/src/feishu/CardRenderer.ts +313 -35
- package/src/feishu/FeishuMarkdown.ts +27 -1
- package/src/feishu/FeishuMessageClient.ts +33 -0
- package/src/feishu/FeishuTurnPresenter.ts +43 -6
- package/src/feishu/types.ts +1 -0
- package/src/index.ts +174 -2
- package/src/presentation/OutboundRouter.ts +7 -0
- package/src/presentation/TurnStateReducer.ts +21 -7
- package/src/proxy/ProxySessionController.ts +661 -16
- package/src/runtime/types.ts +1 -0
- package/src/state/StateStore.ts +56 -0
|
@@ -71,6 +71,12 @@ const HELP_COMMAND_SECTIONS = [
|
|
|
71
71
|
requiresArgument: true,
|
|
72
72
|
},
|
|
73
73
|
{ command: "/sessions", usage: "[关键词]", description: "查找本机任务" },
|
|
74
|
+
{
|
|
75
|
+
command: "/archive",
|
|
76
|
+
usage: "[序号或任务 ID]",
|
|
77
|
+
description: "归档当前或指定任务",
|
|
78
|
+
},
|
|
79
|
+
{ command: "/dismiss", description: "归档当前任务并解散当前群" },
|
|
74
80
|
{
|
|
75
81
|
command: "/switch",
|
|
76
82
|
usage: "[序号或任务 ID]",
|
|
@@ -268,7 +274,7 @@ export class ProxySessionController {
|
|
|
268
274
|
if (isQueueIndependentCommand(command)) {
|
|
269
275
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
270
276
|
try {
|
|
271
|
-
if (command.type !== "forkgroup")
|
|
277
|
+
if (command.type !== "forkgroup" && command.type !== "dismiss")
|
|
272
278
|
await this.ensureThreadFork(message);
|
|
273
279
|
await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
|
|
274
280
|
if (!isPromptCommand(command))
|
|
@@ -413,6 +419,22 @@ export class ProxySessionController {
|
|
|
413
419
|
await this.createFeishuGroupFromReference(contextKey, sessionId, scopedAction.userId);
|
|
414
420
|
await this.refreshSessionsCardFromAction(scopedAction);
|
|
415
421
|
}
|
|
422
|
+
else if (kind === "session_archive") {
|
|
423
|
+
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
424
|
+
await this.archiveSessionReference(contextKey, sessionId, {
|
|
425
|
+
announce: true,
|
|
426
|
+
source: "sessions_card",
|
|
427
|
+
});
|
|
428
|
+
await this.refreshSessionsCardFromAction(scopedAction);
|
|
429
|
+
}
|
|
430
|
+
else if (kind === "group_dismiss_keep") {
|
|
431
|
+
this.assertDismissGroupRequester(scopedAction);
|
|
432
|
+
await this.outbound.updateInteractiveCard(contextKey, requiredCardMessageId(scopedAction.messageId), this.cardRenderer.renderDismissGroupKept());
|
|
433
|
+
}
|
|
434
|
+
else if (kind === "group_dismiss_confirm") {
|
|
435
|
+
this.assertDismissGroupRequester(scopedAction);
|
|
436
|
+
await this.dismissGroupAndArchiveTask(contextKey, String(scopedAction.value.sessionId ?? ""), "command");
|
|
437
|
+
}
|
|
416
438
|
else if (kind === "session_stop") {
|
|
417
439
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
418
440
|
await this.stopSessionReference(contextKey, sessionId);
|
|
@@ -557,6 +579,19 @@ export class ProxySessionController {
|
|
|
557
579
|
await this.cancelSession(record);
|
|
558
580
|
return `Task stop requested: ${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
|
|
559
581
|
}
|
|
582
|
+
async controlArchiveTask(localSessionId) {
|
|
583
|
+
const record = this.requireControlSession(localSessionId);
|
|
584
|
+
return this.archiveSessionReference(this.controlSessionContextKey(record), localSessionId, {
|
|
585
|
+
announce: false,
|
|
586
|
+
source: "cli",
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
async controlDismissTask(localSessionId) {
|
|
590
|
+
const record = this.store.getSession(localSessionId);
|
|
591
|
+
if (!record)
|
|
592
|
+
throw new Error(`Task not found: ${localSessionId}`);
|
|
593
|
+
return this.dismissGroupAndArchiveTask(this.controlSessionContextKey(record), localSessionId, "cli");
|
|
594
|
+
}
|
|
560
595
|
async controlGetTaskStatus(localSessionId) {
|
|
561
596
|
const record = this.store.getSession(localSessionId);
|
|
562
597
|
if (!record)
|
|
@@ -706,6 +741,314 @@ export class ProxySessionController {
|
|
|
706
741
|
task: forked.task,
|
|
707
742
|
};
|
|
708
743
|
}
|
|
744
|
+
async controlCreateTask(localSessionId, requestedTitle, requestedProjectCwd, forceProjectless = false, requestedAgentName) {
|
|
745
|
+
const source = this.requireControlSession(localSessionId);
|
|
746
|
+
const contextKey = this.controlSessionContextKey(source);
|
|
747
|
+
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
748
|
+
const agentName = requestedAgentName?.trim() || context.defaultAgent;
|
|
749
|
+
const agent = this.ensureAgent(agentName);
|
|
750
|
+
if (forceProjectless && agent.kind !== "app-server") {
|
|
751
|
+
throw new Error("task new --nodir is only available for App Server agents.");
|
|
752
|
+
}
|
|
753
|
+
const replyTarget = this.controlSessionReplyTarget(source);
|
|
754
|
+
const cwd = forceProjectless
|
|
755
|
+
? undefined
|
|
756
|
+
: requestedProjectCwd === undefined
|
|
757
|
+
? detectProjectlessWorkspace(source.cwd)
|
|
758
|
+
? agent.kind === "app-server" ? undefined : createProjectlessWorkspace().cwd
|
|
759
|
+
: source.cwd
|
|
760
|
+
: resolveUserPath(requestedProjectCwd, source.cwd);
|
|
761
|
+
const settings = source.agentName === agentName
|
|
762
|
+
? {
|
|
763
|
+
modelProvider: source.modelProvider,
|
|
764
|
+
model: source.model,
|
|
765
|
+
reasoningEffort: source.reasoningEffort,
|
|
766
|
+
permissionMode: source.permissionMode,
|
|
767
|
+
}
|
|
768
|
+
: {};
|
|
769
|
+
return this.outbound.withReplyTarget(contextKey, replyTarget, () => this.createSession(contextKey, agentName, cwd, true, false, undefined, undefined, requestedTitle, settings));
|
|
770
|
+
}
|
|
771
|
+
async controlForkTask(localSessionId) {
|
|
772
|
+
const source = this.requireControlSession(localSessionId);
|
|
773
|
+
const contextKey = this.controlSessionContextKey(source);
|
|
774
|
+
const replyTarget = this.controlSessionReplyTarget(source);
|
|
775
|
+
const plan = await this.prepareForkSession(contextKey, source.localSessionId);
|
|
776
|
+
const forked = await this.outbound.withReplyTarget(contextKey, replyTarget, async () => {
|
|
777
|
+
const result = await this.forkSessionIntoContext(contextKey, plan);
|
|
778
|
+
await this.outbound.sendText(contextKey, `已从指定任务${plan.forkedFromHistoricalTurn ? "最近已完成轮次" : ""}创建分支并切换到新任务:${result.session.title
|
|
779
|
+
? `${result.session.title}(${result.session.remoteSessionId})`
|
|
780
|
+
: result.session.remoteSessionId}`);
|
|
781
|
+
return result;
|
|
782
|
+
});
|
|
783
|
+
return {
|
|
784
|
+
sourceLocalSessionId: source.localSessionId,
|
|
785
|
+
sourceTurnId: plan.lastTurnId,
|
|
786
|
+
task: this.store.getSession(forked.record.localSessionId) ?? forked.record,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
async controlSwitchTask(localSessionId, targetLocalSessionId, previous = false) {
|
|
790
|
+
const anchor = this.requireControlSession(localSessionId);
|
|
791
|
+
const contextKey = this.controlSessionContextKey(anchor);
|
|
792
|
+
const replyTarget = this.controlSessionReplyTarget(anchor);
|
|
793
|
+
const target = previous ? undefined : targetLocalSessionId ?? anchor.localSessionId;
|
|
794
|
+
await this.outbound.withReplyTarget(contextKey, replyTarget, () => this.switchSession(contextKey, target));
|
|
795
|
+
return this.requireCurrentSession(contextKey);
|
|
796
|
+
}
|
|
797
|
+
async controlQueueTaskPrompt(localSessionId, text) {
|
|
798
|
+
const promptText = text.trim();
|
|
799
|
+
if (!promptText)
|
|
800
|
+
throw new Error("The queued Prompt cannot be empty.");
|
|
801
|
+
const record = this.requireControlSession(localSessionId);
|
|
802
|
+
const contextKey = this.controlSessionContextKey(record);
|
|
803
|
+
const replyTarget = this.controlSessionReplyTarget(record);
|
|
804
|
+
const queued = this.persistQueuedPrompt(record.localSessionId, contextKey, promptText, { replyTarget });
|
|
805
|
+
this.store.audit(contextKey, "queued_prompt_added", {
|
|
806
|
+
promptId: queued.promptId,
|
|
807
|
+
localSessionId: record.localSessionId,
|
|
808
|
+
source: "cli",
|
|
809
|
+
});
|
|
810
|
+
const queuedCount = this.store.countQueuedPrompts(record.localSessionId);
|
|
811
|
+
await this.scheduleNextQueuedPrompt(record.localSessionId);
|
|
812
|
+
return { promptId: queued.promptId, queued: queuedCount };
|
|
813
|
+
}
|
|
814
|
+
controlTaskAgent(localSessionId, agentName) {
|
|
815
|
+
const record = this.requireControlSession(localSessionId);
|
|
816
|
+
const contextKey = this.controlSessionContextKey(record);
|
|
817
|
+
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
818
|
+
if (agentName) {
|
|
819
|
+
this.ensureAgent(agentName);
|
|
820
|
+
this.store.setDefaultAgent(contextKey, agentName);
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
current: agentName ?? context.defaultAgent,
|
|
824
|
+
agents: Object.entries(this.config.agents).map(([name, agent]) => ({ name, title: agent.title })),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
async controlTaskSettings(localSessionId, setting, value) {
|
|
828
|
+
const record = this.requireControlSession(localSessionId);
|
|
829
|
+
const loaded = await this.loadSession(record);
|
|
830
|
+
if (setting && !value?.trim())
|
|
831
|
+
throw new Error(`task ${setting} requires a value.`);
|
|
832
|
+
const nextValue = value?.trim();
|
|
833
|
+
if (setting === "provider" && nextValue) {
|
|
834
|
+
await this.assertModelProvider(loaded, nextValue);
|
|
835
|
+
const models = await loaded.runtime.listModels();
|
|
836
|
+
const model = models.find((candidate) => candidate.id === loaded.session.model)
|
|
837
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
838
|
+
if (!model)
|
|
839
|
+
throw new Error("The current runtime has no model available for a Provider change.");
|
|
840
|
+
const effort = model.supportedReasoningEfforts.some((candidate) => candidate.value === loaded.session.reasoningEffort)
|
|
841
|
+
? loaded.session.reasoningEffort
|
|
842
|
+
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
843
|
+
if (!effort)
|
|
844
|
+
throw new Error(`Model ${model.id} has no reasoning effort available for a Provider change.`);
|
|
845
|
+
if (!loaded.runtime.setExecutionSettings)
|
|
846
|
+
throw new Error("The current runtime does not support Provider settings.");
|
|
847
|
+
const updated = await loaded.runtime.setExecutionSettings(record.localSessionId, {
|
|
848
|
+
modelProvider: nextValue,
|
|
849
|
+
model: model.id,
|
|
850
|
+
reasoningEffort: effort,
|
|
851
|
+
permissionMode: loaded.session.permissionMode,
|
|
852
|
+
});
|
|
853
|
+
this.store.updateRuntimeSession(record.localSessionId, {
|
|
854
|
+
modelProvider: updated.modelProvider ?? nextValue,
|
|
855
|
+
model: updated.model ?? model.id,
|
|
856
|
+
reasoningEffort: updated.reasoningEffort ?? effort,
|
|
857
|
+
permissionMode: updated.permissionMode,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
else if (setting === "model" && nextValue) {
|
|
861
|
+
const models = await loaded.runtime.listModels();
|
|
862
|
+
const selected = models.find((candidate) => candidate.id === nextValue);
|
|
863
|
+
if (!selected)
|
|
864
|
+
throw new Error(`Unknown model: ${nextValue}`);
|
|
865
|
+
const currentEffort = loaded.session.reasoningEffort;
|
|
866
|
+
const nextEffort = currentEffort && selected.supportedReasoningEfforts.some((candidate) => candidate.value === currentEffort)
|
|
867
|
+
? currentEffort
|
|
868
|
+
: selected.defaultReasoningEffort;
|
|
869
|
+
await loaded.runtime.setModel(record.localSessionId, nextValue);
|
|
870
|
+
if (nextEffort && nextEffort !== currentEffort) {
|
|
871
|
+
await loaded.runtime.setReasoningEffort(record.localSessionId, nextEffort);
|
|
872
|
+
}
|
|
873
|
+
this.store.updateRuntimeSession(record.localSessionId, { model: nextValue, reasoningEffort: nextEffort });
|
|
874
|
+
}
|
|
875
|
+
else if (setting === "thinking" && nextValue) {
|
|
876
|
+
const models = await loaded.runtime.listModels();
|
|
877
|
+
const currentModel = models.find((candidate) => candidate.id === loaded.session.model)
|
|
878
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
879
|
+
if (!currentModel)
|
|
880
|
+
throw new Error("The current runtime has no model with configurable reasoning effort.");
|
|
881
|
+
if (!currentModel.supportedReasoningEfforts.some((candidate) => candidate.value === nextValue)) {
|
|
882
|
+
const supported = currentModel.supportedReasoningEfforts.map((candidate) => candidate.value).join(", ") || "none";
|
|
883
|
+
throw new Error(`Unsupported reasoning effort: ${nextValue}. Supported values: ${supported}.`);
|
|
884
|
+
}
|
|
885
|
+
await loaded.runtime.setReasoningEffort(record.localSessionId, nextValue);
|
|
886
|
+
this.store.updateRuntimeSession(record.localSessionId, { reasoningEffort: nextValue });
|
|
887
|
+
}
|
|
888
|
+
else if (setting === "permissions" && nextValue) {
|
|
889
|
+
if (nextValue !== "auto" && nextValue !== "confirm") {
|
|
890
|
+
throw new Error("task permissions accepts auto or confirm.");
|
|
891
|
+
}
|
|
892
|
+
await loaded.runtime.setPermissionMode(record.localSessionId, nextValue);
|
|
893
|
+
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: nextValue });
|
|
894
|
+
}
|
|
895
|
+
const session = this.store.getSession(record.localSessionId) ?? record;
|
|
896
|
+
const models = await loaded.runtime.listModels();
|
|
897
|
+
const currentModel = models.find((candidate) => candidate.id === session.model)
|
|
898
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
899
|
+
const providers = loaded.runtime.listModelProviders
|
|
900
|
+
? await loaded.runtime.listModelProviders()
|
|
901
|
+
: [];
|
|
902
|
+
return {
|
|
903
|
+
session,
|
|
904
|
+
providers,
|
|
905
|
+
models,
|
|
906
|
+
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
907
|
+
permissionModes: ["auto", "confirm"],
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
async controlTaskGoal(localSessionId, action, objective) {
|
|
911
|
+
const record = this.requireControlSession(localSessionId);
|
|
912
|
+
const loaded = await this.loadSession(record);
|
|
913
|
+
if (loaded.runtime.kind !== "codex" || !loaded.runtime.getGoal || !loaded.runtime.setGoal || !loaded.runtime.clearGoal) {
|
|
914
|
+
throw new Error("The current Agent does not support Goal mode.");
|
|
915
|
+
}
|
|
916
|
+
if (action === "show")
|
|
917
|
+
return { goal: await loaded.runtime.getGoal(record.localSessionId) };
|
|
918
|
+
if (action === "clear")
|
|
919
|
+
return { cleared: await loaded.runtime.clearGoal(record.localSessionId) };
|
|
920
|
+
const current = await loaded.runtime.getGoal(record.localSessionId);
|
|
921
|
+
if (action === "pause" || action === "resume") {
|
|
922
|
+
if (!current)
|
|
923
|
+
throw new Error("The task has no Goal.");
|
|
924
|
+
return { goal: await loaded.runtime.setGoal(record.localSessionId, { status: action === "pause" ? "paused" : "active" }) };
|
|
925
|
+
}
|
|
926
|
+
const nextObjective = objective?.trim();
|
|
927
|
+
if (!nextObjective)
|
|
928
|
+
throw new Error(`task goal ${action} requires an objective.`);
|
|
929
|
+
validateGoalObjective(nextObjective);
|
|
930
|
+
if (action === "set" && current && current.status !== "complete") {
|
|
931
|
+
throw new Error("The task already has an unfinished Goal. Use task goal <task> edit <objective>, or clear it first.");
|
|
932
|
+
}
|
|
933
|
+
if (action === "edit" && !current)
|
|
934
|
+
throw new Error("The task has no Goal to edit.");
|
|
935
|
+
return {
|
|
936
|
+
goal: await loaded.runtime.setGoal(record.localSessionId, {
|
|
937
|
+
objective: nextObjective,
|
|
938
|
+
status: action === "edit" ? current.status : "active",
|
|
939
|
+
...(action === "edit" ? { tokenBudget: current.tokenBudget } : {}),
|
|
940
|
+
}),
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
controlListTaskTurns(localSessionId) {
|
|
944
|
+
const session = this.requireControlSession(localSessionId);
|
|
945
|
+
if (!session.remoteSessionId || !this.isCodexSession(session)) {
|
|
946
|
+
throw new Error("The task is not an App Server task that can be reset.");
|
|
947
|
+
}
|
|
948
|
+
const contextKey = this.controlSessionContextKey(session);
|
|
949
|
+
const rows = this.store.listCompletedTurnGraph(session.localSessionId, contextKey);
|
|
950
|
+
const graph = buildTurnGraphRows(rows.map((turn) => ({ turnId: turn.turnId, parentTurnId: turn.parentTurnId })));
|
|
951
|
+
return {
|
|
952
|
+
session,
|
|
953
|
+
turns: rows.flatMap((turn, index) => {
|
|
954
|
+
const snapshot = turnViewSnapshot(turn.snapshot);
|
|
955
|
+
if (!snapshot)
|
|
956
|
+
return [];
|
|
957
|
+
return [{
|
|
958
|
+
sequence: graph[index].sequence,
|
|
959
|
+
turnId: turn.turnId,
|
|
960
|
+
parentTurnId: turn.parentTurnId,
|
|
961
|
+
prompt: snapshot.prompt ?? snapshot.taskTitle,
|
|
962
|
+
startedAt: snapshot.startedAt,
|
|
963
|
+
completedAt: snapshot.completedAt,
|
|
964
|
+
current: session.lastTurnId === turn.turnId && session.lastTurnStatus === "completed",
|
|
965
|
+
}];
|
|
966
|
+
}),
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
async controlResetTaskToTurn(localSessionId, turnId) {
|
|
970
|
+
const record = this.requireControlSession(localSessionId);
|
|
971
|
+
const contextKey = this.controlSessionContextKey(record);
|
|
972
|
+
const previous = this.sessionResets.get(record.localSessionId) ?? Promise.resolve();
|
|
973
|
+
const reset = previous.catch(() => undefined).then(() => this.performSessionReset(contextKey, record, turnId, false));
|
|
974
|
+
this.sessionResets.set(record.localSessionId, reset);
|
|
975
|
+
try {
|
|
976
|
+
await reset;
|
|
977
|
+
}
|
|
978
|
+
finally {
|
|
979
|
+
if (this.sessionResets.get(record.localSessionId) === reset)
|
|
980
|
+
this.sessionResets.delete(record.localSessionId);
|
|
981
|
+
}
|
|
982
|
+
return this.store.getSession(record.localSessionId) ?? record;
|
|
983
|
+
}
|
|
984
|
+
controlTaskMute(localSessionId, enabled) {
|
|
985
|
+
const record = this.requireControlSession(localSessionId);
|
|
986
|
+
const contextKey = baseChatContextKey(this.controlSessionContextKey(record));
|
|
987
|
+
const chat = this.store.getChatContext(contextKey);
|
|
988
|
+
if (chat?.chatType !== "group")
|
|
989
|
+
throw new Error("task mute is only available for group chats.");
|
|
990
|
+
const next = enabled ?? Boolean(chat.requiresMention);
|
|
991
|
+
if (enabled !== undefined)
|
|
992
|
+
this.store.setChatRequiresMention(contextKey, enabled);
|
|
993
|
+
return { contextKey, enabled: next };
|
|
994
|
+
}
|
|
995
|
+
async controlRunTaskShell(localSessionId, command) {
|
|
996
|
+
const record = this.requireControlSession(localSessionId);
|
|
997
|
+
const normalized = command.trim();
|
|
998
|
+
if (!normalized)
|
|
999
|
+
throw new Error("task shell requires a command.");
|
|
1000
|
+
return { ...await this.shellCommandExecutor(normalized, record.cwd), cwd: record.cwd, command: normalized };
|
|
1001
|
+
}
|
|
1002
|
+
async controlListTaskDirectory(localSessionId, requestedDirectory, requestedPage = 0) {
|
|
1003
|
+
const record = this.requireControlSession(localSessionId);
|
|
1004
|
+
if (requestedDirectory === WINDOWS_DRIVES_DIRECTORY) {
|
|
1005
|
+
const drives = await this.windowsDriveLister();
|
|
1006
|
+
const totalPages = Math.max(1, Math.ceil(drives.length / DIRECTORY_PAGE_SIZE));
|
|
1007
|
+
const page = Math.min(Math.max(0, Math.trunc(requestedPage)), totalPages - 1);
|
|
1008
|
+
return {
|
|
1009
|
+
directory: WINDOWS_DRIVES_DIRECTORY,
|
|
1010
|
+
page,
|
|
1011
|
+
totalPages,
|
|
1012
|
+
totalEntries: drives.length,
|
|
1013
|
+
entries: drives.slice(page * DIRECTORY_PAGE_SIZE, (page + 1) * DIRECTORY_PAGE_SIZE).map((drive) => ({
|
|
1014
|
+
name: windowsDriveDisplayName(drive),
|
|
1015
|
+
path: drive.root,
|
|
1016
|
+
kind: "drive",
|
|
1017
|
+
})),
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
const directory = requestedDirectory === undefined
|
|
1021
|
+
? path.resolve(record.cwd)
|
|
1022
|
+
: resolveUserPath(requestedDirectory, record.cwd);
|
|
1023
|
+
await this.assertBrowsableDirectory(directory);
|
|
1024
|
+
const entries = (await fs.readdir(directory, { withFileTypes: true }))
|
|
1025
|
+
.map((entry) => ({
|
|
1026
|
+
name: entry.name,
|
|
1027
|
+
path: path.join(directory, entry.name),
|
|
1028
|
+
kind: directoryBrowserEntryKind(entry),
|
|
1029
|
+
}))
|
|
1030
|
+
.sort((left, right) => Number(right.kind === "directory") - Number(left.kind === "directory")
|
|
1031
|
+
|| left.name.localeCompare(right.name, undefined, { numeric: true, sensitivity: "base" }));
|
|
1032
|
+
const totalPages = Math.max(1, Math.ceil(entries.length / DIRECTORY_PAGE_SIZE));
|
|
1033
|
+
const page = Math.min(Math.max(0, Math.trunc(requestedPage)), totalPages - 1);
|
|
1034
|
+
const parentDirectory = isWindowsDriveRoot(directory) ? WINDOWS_DRIVES_DIRECTORY : path.dirname(directory);
|
|
1035
|
+
return {
|
|
1036
|
+
directory,
|
|
1037
|
+
...(parentDirectory !== directory ? { parentDirectory } : {}),
|
|
1038
|
+
page,
|
|
1039
|
+
totalPages,
|
|
1040
|
+
totalEntries: entries.length,
|
|
1041
|
+
entries: entries.slice(page * DIRECTORY_PAGE_SIZE, (page + 1) * DIRECTORY_PAGE_SIZE),
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async controlSendTaskFile(localSessionId, requestedFilePath) {
|
|
1045
|
+
const record = this.requireControlSession(localSessionId);
|
|
1046
|
+
const filePath = resolveUserPath(requestedFilePath, record.cwd);
|
|
1047
|
+
await this.assertSendableFile(filePath);
|
|
1048
|
+
const contextKey = this.controlSessionContextKey(record);
|
|
1049
|
+
const replyTarget = this.controlSessionReplyTarget(record);
|
|
1050
|
+
return this.outbound.withReplyTarget(contextKey, replyTarget, () => this.outbound.sendFile(contextKey, filePath));
|
|
1051
|
+
}
|
|
709
1052
|
cardActionContextKey(action) {
|
|
710
1053
|
const explicit = typeof action.value.contextKey === "string" ? action.value.contextKey : undefined;
|
|
711
1054
|
if (explicit && baseChatContextKey(explicit) === baseChatContextKey(action.contextKey))
|
|
@@ -776,6 +1119,19 @@ export class ProxySessionController {
|
|
|
776
1119
|
else
|
|
777
1120
|
await this.openAgentSettings(contextKey);
|
|
778
1121
|
return;
|
|
1122
|
+
case "archive": {
|
|
1123
|
+
const sessionId = command.sessionId ?? context.currentSessionId;
|
|
1124
|
+
if (!sessionId)
|
|
1125
|
+
throw new Error("当前会话没有可归档的任务。请使用 /sessions 查找任务。");
|
|
1126
|
+
await this.archiveSessionReference(contextKey, sessionId, {
|
|
1127
|
+
announce: true,
|
|
1128
|
+
source: "command",
|
|
1129
|
+
});
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
case "dismiss":
|
|
1133
|
+
await this.openDismissGroupCard(contextKey, userId);
|
|
1134
|
+
return;
|
|
779
1135
|
case "status":
|
|
780
1136
|
await this.status(contextKey, command.sessionId);
|
|
781
1137
|
return;
|
|
@@ -2390,15 +2746,19 @@ export class ProxySessionController {
|
|
|
2390
2746
|
if (current.localSessionId !== sessionId) {
|
|
2391
2747
|
throw new Error("这张思考卡片不属于当前任务。请先切换回对应任务,再点击 Reset。");
|
|
2392
2748
|
}
|
|
2749
|
+
await this.performSessionReset(contextKey, current, turnId, true);
|
|
2750
|
+
}
|
|
2751
|
+
async performSessionReset(contextKey, current, turnId, announce) {
|
|
2393
2752
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
2394
2753
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
2395
2754
|
}
|
|
2396
2755
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
2397
|
-
if (!snapshot || snapshot.sessionId !==
|
|
2756
|
+
if (!snapshot || snapshot.sessionId !== current.localSessionId || snapshot.status !== "completed") {
|
|
2398
2757
|
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
2399
2758
|
}
|
|
2400
2759
|
if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
|
|
2401
|
-
|
|
2760
|
+
if (announce)
|
|
2761
|
+
await this.outbound.sendText(contextKey, "当前任务已经处于本轮完成后的对话状态,无需 Reset。");
|
|
2402
2762
|
return;
|
|
2403
2763
|
}
|
|
2404
2764
|
const runtime = this.runtimes.forAgent(current.agentName);
|
|
@@ -2413,7 +2773,7 @@ export class ProxySessionController {
|
|
|
2413
2773
|
throw new Error("当前任务仍在执行。请等待任务完成或先停止任务,再点击 Reset。");
|
|
2414
2774
|
}
|
|
2415
2775
|
const origin = this.store.getTurnRuntimeOrigin(turnId);
|
|
2416
|
-
if (origin && (origin.localSessionId !==
|
|
2776
|
+
if (origin && (origin.localSessionId !== current.localSessionId || origin.agentName !== current.agentName)) {
|
|
2417
2777
|
throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
|
|
2418
2778
|
}
|
|
2419
2779
|
const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
|
|
@@ -2445,13 +2805,15 @@ export class ProxySessionController {
|
|
|
2445
2805
|
});
|
|
2446
2806
|
const targetSummary = truncateText((snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(), 100) || "未记录对话内容";
|
|
2447
2807
|
const targetTime = snapshot.completedAt ?? snapshot.startedAt;
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2808
|
+
if (announce) {
|
|
2809
|
+
await this.outbound.sendText(contextKey, [
|
|
2810
|
+
"已将当前任务重置到:",
|
|
2811
|
+
targetSummary,
|
|
2812
|
+
`完成时间:${targetTime === undefined ? "未知" : formatResetTurnTime(targetTime)}`,
|
|
2813
|
+
`Turn ID:${turnId}`,
|
|
2814
|
+
"后续对话将从该轮完成后的状态继续;本地文件没有回退。",
|
|
2815
|
+
].join("\n"));
|
|
2816
|
+
}
|
|
2455
2817
|
}
|
|
2456
2818
|
async resolveApproval(action) {
|
|
2457
2819
|
const sessionId = String(action.value.sessionId ?? "");
|
|
@@ -3268,6 +3630,16 @@ export class ProxySessionController {
|
|
|
3268
3630
|
contextKey,
|
|
3269
3631
|
},
|
|
3270
3632
|
});
|
|
3633
|
+
actions.push({
|
|
3634
|
+
text: "Archive",
|
|
3635
|
+
value: {
|
|
3636
|
+
action: "session_archive",
|
|
3637
|
+
sessionId: entry.reference,
|
|
3638
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
3639
|
+
page: String(page),
|
|
3640
|
+
contextKey,
|
|
3641
|
+
},
|
|
3642
|
+
});
|
|
3271
3643
|
const title = truncateText(entry.title.replace(/\s+/gu, " ").trim() || "未命名任务", 56);
|
|
3272
3644
|
return {
|
|
3273
3645
|
project,
|
|
@@ -3306,7 +3678,7 @@ export class ProxySessionController {
|
|
|
3306
3678
|
`第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
|
|
3307
3679
|
"",
|
|
3308
3680
|
"> 项目行:**New** 新建任务,**NewGroup** 新建群。",
|
|
3309
|
-
"> 任务详情:**Switch** 切换,**Stop** 停止,**Fork** / **ForkGroup** 创建分支,**Status**
|
|
3681
|
+
"> 任务详情:**Switch** 切换,**Stop** 停止,**Fork** / **ForkGroup** 创建分支,**Status** 查看状态,**Archive** 归档。",
|
|
3310
3682
|
], [
|
|
3311
3683
|
...(hasPrevious ? [{
|
|
3312
3684
|
text: "Previous",
|
|
@@ -3471,6 +3843,138 @@ export class ProxySessionController {
|
|
|
3471
3843
|
});
|
|
3472
3844
|
await this.outbound.sendText(contextKey, `已向 Agent 发送 Interrupt 请求:${turnId}`);
|
|
3473
3845
|
}
|
|
3846
|
+
async archiveSessionReference(contextKey, reference, options) {
|
|
3847
|
+
const taskId = this.resolveSessionReference(contextKey, reference);
|
|
3848
|
+
const direct = this.store.getSession(taskId);
|
|
3849
|
+
if (direct?.status === "closed")
|
|
3850
|
+
throw new Error(`任务已经归档:${direct.title ?? taskId}`);
|
|
3851
|
+
const local = direct ?? this.findStoredSessionByReference(taskId);
|
|
3852
|
+
if (local && (!local.remoteSessionId || !this.isCodexSession(local))) {
|
|
3853
|
+
throw new Error("当前 Agent 不支持归档任务。");
|
|
3854
|
+
}
|
|
3855
|
+
if (local && (local.status === "running"
|
|
3856
|
+
|| Boolean(this.runtimes.forAgent(local.agentName).getSession(local.localSessionId)?.activeTurnId))) {
|
|
3857
|
+
throw new Error("任务正在执行,无法归档。请先停止任务或等待本轮完成。");
|
|
3858
|
+
}
|
|
3859
|
+
if (local && this.store.countQueuedPrompts(local.localSessionId) > 0) {
|
|
3860
|
+
throw new Error("任务仍有排队消息,无法归档。请先处理或取消排队消息。");
|
|
3861
|
+
}
|
|
3862
|
+
const remoteReference = local
|
|
3863
|
+
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
3864
|
+
: taskId;
|
|
3865
|
+
const { agentName, runtime, remote } = await this.resolveRemoteCodexSession(remoteReference);
|
|
3866
|
+
if (isRemoteSessionActive(remote)) {
|
|
3867
|
+
throw new Error("任务正在执行,无法归档。请先停止任务或等待本轮完成。");
|
|
3868
|
+
}
|
|
3869
|
+
if (!runtime.archiveRemoteSession) {
|
|
3870
|
+
throw new Error(`Agent ${agentName} 不支持归档任务。`);
|
|
3871
|
+
}
|
|
3872
|
+
const wasCurrent = Boolean(local && this.store.getUserContext(contextKey)?.currentSessionId === local.localSessionId);
|
|
3873
|
+
await runtime.archiveRemoteSession(remote.id);
|
|
3874
|
+
const title = local?.title ?? remote.title ?? remote.preview ?? remote.id;
|
|
3875
|
+
if (local) {
|
|
3876
|
+
this.store.archiveSession(local.localSessionId);
|
|
3877
|
+
this.outbound.unregisterSession(local.localSessionId);
|
|
3878
|
+
}
|
|
3879
|
+
this.store.audit(contextKey, "session_archived", {
|
|
3880
|
+
...(local ? { localSessionId: local.localSessionId } : {}),
|
|
3881
|
+
remoteSessionId: remote.id,
|
|
3882
|
+
agentName,
|
|
3883
|
+
source: options.source,
|
|
3884
|
+
});
|
|
3885
|
+
if (options.announce) {
|
|
3886
|
+
await this.outbound.sendText(contextKey, wasCurrent
|
|
3887
|
+
? `已归档任务:${title}\n当前会话已没有绑定任务,直接发送消息即可创建新任务。`
|
|
3888
|
+
: `已归档任务:${title}`);
|
|
3889
|
+
}
|
|
3890
|
+
return {
|
|
3891
|
+
localSessionId: local?.localSessionId ?? "",
|
|
3892
|
+
remoteSessionId: remote.id,
|
|
3893
|
+
title,
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
async openDismissGroupCard(contextKey, userId) {
|
|
3897
|
+
const groupContextKey = this.dismissibleGroupContextKey(contextKey);
|
|
3898
|
+
if (!userId)
|
|
3899
|
+
throw new Error("无法识别当前用户,不能发起解散群操作。");
|
|
3900
|
+
const session = this.requireCurrentSession(groupContextKey);
|
|
3901
|
+
await this.outbound.sendInteractiveCard(groupContextKey, this.cardRenderer.renderDismissGroupConfirmation({
|
|
3902
|
+
contextKey: groupContextKey,
|
|
3903
|
+
sessionId: session.localSessionId,
|
|
3904
|
+
taskTitle: session.title ?? session.remoteSessionId ?? session.localSessionId,
|
|
3905
|
+
requestedBy: userId,
|
|
3906
|
+
}));
|
|
3907
|
+
}
|
|
3908
|
+
assertDismissGroupRequester(action) {
|
|
3909
|
+
const requestedBy = String(action.value.requestedBy ?? "");
|
|
3910
|
+
if (!requestedBy || !action.userId || action.userId !== requestedBy) {
|
|
3911
|
+
throw new Error("只有发起解散操作的用户可以点击这张卡片。");
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
async dismissGroupAndArchiveTask(contextKey, localSessionId, source) {
|
|
3915
|
+
const groupContextKey = this.dismissibleGroupContextKey(contextKey);
|
|
3916
|
+
const session = this.store.getSessionForContext(localSessionId, groupContextKey);
|
|
3917
|
+
if (!session)
|
|
3918
|
+
throw new Error("解散群卡片已失效,请重新发送 /dismiss。");
|
|
3919
|
+
const currentSessionId = this.store.getUserContext(groupContextKey)?.currentSessionId;
|
|
3920
|
+
if (session.status !== "closed" && currentSessionId !== localSessionId) {
|
|
3921
|
+
throw new Error("当前任务已经切换,解散群卡片已失效,请重新发送 /dismiss。");
|
|
3922
|
+
}
|
|
3923
|
+
this.assertNoOtherActiveGroupTasks(groupContextKey, localSessionId);
|
|
3924
|
+
const archived = session.status === "closed"
|
|
3925
|
+
? {
|
|
3926
|
+
localSessionId: session.localSessionId,
|
|
3927
|
+
remoteSessionId: session.remoteSessionId ?? session.acpSessionId ?? "",
|
|
3928
|
+
title: session.title ?? session.remoteSessionId ?? session.localSessionId,
|
|
3929
|
+
}
|
|
3930
|
+
: await this.archiveSessionReference(groupContextKey, localSessionId, {
|
|
3931
|
+
announce: false,
|
|
3932
|
+
source: "dismiss",
|
|
3933
|
+
});
|
|
3934
|
+
const chatId = groupContextKey.slice("chat_id:".length);
|
|
3935
|
+
try {
|
|
3936
|
+
await this.outbound.deleteGroup(groupContextKey, chatId);
|
|
3937
|
+
}
|
|
3938
|
+
catch (error) {
|
|
3939
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3940
|
+
throw new Error(`当前任务已归档,但解散群失败:${message}`);
|
|
3941
|
+
}
|
|
3942
|
+
const detachedSessionIds = this.store.removeChatContext(groupContextKey);
|
|
3943
|
+
for (const detachedSessionId of detachedSessionIds) {
|
|
3944
|
+
const linked = this.store.getSession(detachedSessionId);
|
|
3945
|
+
const routedContextKey = this.outbound.getSessionContextKey(detachedSessionId) ?? linked?.contextKey;
|
|
3946
|
+
if (routedContextKey && baseChatContextKey(routedContextKey) === groupContextKey) {
|
|
3947
|
+
this.outbound.unregisterSession(detachedSessionId);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
this.store.audit(groupContextKey, "group_dismissed", {
|
|
3951
|
+
chatId,
|
|
3952
|
+
localSessionId,
|
|
3953
|
+
remoteSessionId: archived.remoteSessionId,
|
|
3954
|
+
source,
|
|
3955
|
+
});
|
|
3956
|
+
return { ...archived, chatId };
|
|
3957
|
+
}
|
|
3958
|
+
assertNoOtherActiveGroupTasks(contextKey, dismissedSessionId) {
|
|
3959
|
+
const active = this.store.listSessionsForChat(contextKey).find((session) => (session.localSessionId !== dismissedSessionId
|
|
3960
|
+
&& session.status !== "closed"
|
|
3961
|
+
&& (session.status === "running"
|
|
3962
|
+
|| Boolean(this.runtimes.forAgent(session.agentName).getSession(session.localSessionId)?.activeTurnId)
|
|
3963
|
+
|| this.store.countQueuedPrompts(session.localSessionId) > 0)));
|
|
3964
|
+
if (active) {
|
|
3965
|
+
throw new Error(`群内任务仍在执行或有排队消息,暂时不能解散:${active.title ?? active.localSessionId}`);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
dismissibleGroupContextKey(contextKey) {
|
|
3969
|
+
if (isThreadContextKey(contextKey)) {
|
|
3970
|
+
throw new Error("/dismiss 不能在话题中使用,请回到群聊正文后重试。");
|
|
3971
|
+
}
|
|
3972
|
+
const groupContextKey = baseChatContextKey(contextKey);
|
|
3973
|
+
if (!groupContextKey.startsWith("chat_id:") || this.store.getChatContext(groupContextKey)?.chatType !== "group") {
|
|
3974
|
+
throw new Error("/dismiss 仅适用于普通群聊。");
|
|
3975
|
+
}
|
|
3976
|
+
return groupContextKey;
|
|
3977
|
+
}
|
|
3474
3978
|
resolveSessionReference(contextKey, reference) {
|
|
3475
3979
|
if (reference === undefined) {
|
|
3476
3980
|
const previous = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent).previousSessionId;
|
|
@@ -3770,6 +4274,26 @@ export class ProxySessionController {
|
|
|
3770
4274
|
? "已开启当前群的静音模式。之后只有 @ 机器人的消息会被处理;发送 @机器人 /mute off 可恢复自动响应。"
|
|
3771
4275
|
: "已关闭当前群的静音模式。机器人将恢复自动响应群消息。");
|
|
3772
4276
|
}
|
|
4277
|
+
requireControlSession(localSessionId) {
|
|
4278
|
+
const record = this.store.getSession(localSessionId);
|
|
4279
|
+
if (!record || record.status === "closed")
|
|
4280
|
+
throw new Error(`Task not found: ${localSessionId}`);
|
|
4281
|
+
return record;
|
|
4282
|
+
}
|
|
4283
|
+
controlSessionContextKey(record) {
|
|
4284
|
+
return this.outbound.getSessionContextKey(record.localSessionId)
|
|
4285
|
+
?? (record.lastTurnId ? this.store.getTurnContextKey(record.lastTurnId) : undefined)
|
|
4286
|
+
?? record.contextKey;
|
|
4287
|
+
}
|
|
4288
|
+
controlSessionReplyTarget(record) {
|
|
4289
|
+
const routed = this.outbound.getSessionReplyTarget(record.localSessionId);
|
|
4290
|
+
if (routed)
|
|
4291
|
+
return routed;
|
|
4292
|
+
const snapshot = record.lastTurnId
|
|
4293
|
+
? turnViewSnapshot(this.store.getTurnSnapshot(record.lastTurnId))
|
|
4294
|
+
: undefined;
|
|
4295
|
+
return snapshot?.replyTarget;
|
|
4296
|
+
}
|
|
3773
4297
|
ensureAgent(agentName) {
|
|
3774
4298
|
const agent = this.config.agents[agentName];
|
|
3775
4299
|
if (!agent)
|
|
@@ -4276,7 +4800,7 @@ function isBotOwnedActiveTurn(record, remote) {
|
|
|
4276
4800
|
&& record.lastTurnId === remote.lastTurnId;
|
|
4277
4801
|
}
|
|
4278
4802
|
function isQueueIndependentCommand(command) {
|
|
4279
|
-
if (["stop", "status", "restart", "mute", "help", "sessions", "dir", "goal", "nosteer"].includes(command.type))
|
|
4803
|
+
if (["archive", "dismiss", "stop", "status", "restart", "mute", "help", "sessions", "dir", "goal", "nosteer"].includes(command.type))
|
|
4280
4804
|
return true;
|
|
4281
4805
|
if (command.type === "agent")
|
|
4282
4806
|
return command.agent === undefined;
|