@keyou007/agent-bot 0.1.11 → 0.1.12
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/.env.example +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +41 -35
- package/README.zh.md +41 -35
- package/config.example.yaml +11 -1
- package/dist/cli/FeishuAppConfiguration.js +1 -1
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +2 -2
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +1 -1
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/ServerStarter.js +7 -2
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +20 -16
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +95 -0
- package/dist/cli/taskGroupOptions.js.map +1 -0
- package/dist/cli.js +74 -2
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +35 -9
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/config/loadConfig.js +11 -1
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +6 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/console/ConsoleConnector.js +1 -1
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +14 -7
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/deprecated-cli.js +5 -0
- package/dist/deprecated-cli.js.map +1 -0
- package/dist/feishu/CardRenderer.js +5 -4
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +9 -5
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/transport.js +1 -1
- package/dist/feishu/transport.js.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +2 -2
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +2 -1
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +78 -22
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -1
- package/dist/supervision/systemEnvironment.js +129 -0
- package/dist/supervision/systemEnvironment.js.map +1 -0
- package/dist/supervisor.js +13 -1
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +21 -17
- package/docs/technical-reference.zh.md +21 -17
- package/npm-shrinkwrap.json +4 -3
- package/package.json +3 -2
- package/skills/agent-bot/SKILL.md +61 -28
- package/src/cli/FeishuAppConfiguration.ts +2 -2
- package/src/cli/FeishuAppRegistration.ts +4 -4
- package/src/cli/Initializer.ts +2 -2
- package/src/cli/ServerStarter.ts +8 -3
- package/src/cli/controlProtocol.ts +21 -1
- package/src/cli/help.ts +20 -16
- package/src/cli/taskGroupOptions.ts +149 -0
- package/src/cli.ts +98 -4
- package/src/codex/CodexRuntime.ts +46 -13
- package/src/config/loadConfig.ts +11 -1
- package/src/config/schema.ts +7 -1
- package/src/console/ConsoleConnector.ts +1 -1
- package/src/console/ConsoleTurnPresenter.ts +20 -7
- package/src/deprecated-cli.ts +9 -0
- package/src/feishu/CardRenderer.ts +5 -4
- package/src/feishu/FeishuTurnPresenter.ts +15 -1
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +33 -2
- package/src/presentation/OutboundRouter.ts +15 -3
- package/src/presentation/TurnStateReducer.ts +2 -0
- package/src/presentation/turnViewTypes.ts +1 -0
- package/src/proxy/ProxySessionController.ts +166 -22
- package/src/runtime/createAgentRuntimeRegistry.ts +1 -1
- package/src/supervision/systemEnvironment.ts +174 -0
- package/src/supervisor.ts +12 -1
|
@@ -148,10 +148,23 @@ interface ResolvedThreadForkAnchor {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
interface CreatedFeishuGroupContext {
|
|
151
|
+
chatId: string;
|
|
151
152
|
contextKey: string;
|
|
152
153
|
name: string;
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
interface CreatedFeishuTaskGroup {
|
|
157
|
+
group: CreatedFeishuGroupContext;
|
|
158
|
+
task: SessionRecord;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ControlTaskGroupResult {
|
|
162
|
+
sourceLocalSessionId: string;
|
|
163
|
+
sourceTurnId?: string;
|
|
164
|
+
group: CreatedFeishuGroupContext;
|
|
165
|
+
task: SessionRecord;
|
|
166
|
+
}
|
|
167
|
+
|
|
155
168
|
export interface ProxyLifecycle {
|
|
156
169
|
supervised?: boolean;
|
|
157
170
|
restart(contextKey: string, force: boolean): Promise<void>;
|
|
@@ -615,6 +628,7 @@ export class ProxySessionController {
|
|
|
615
628
|
responseContextKey,
|
|
616
629
|
scopedRecord.title,
|
|
617
630
|
scopedRecord.cwd,
|
|
631
|
+
this.agentLabel(scopedRecord.agentName),
|
|
618
632
|
);
|
|
619
633
|
const promptMessageId = await this.outbound.withReplyTarget(
|
|
620
634
|
responseContextKey,
|
|
@@ -641,6 +655,82 @@ export class ProxySessionController {
|
|
|
641
655
|
return `The Prompt was posted to the original chat and submitted to the task: ${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
|
|
642
656
|
}
|
|
643
657
|
|
|
658
|
+
async controlCreateTaskGroup(
|
|
659
|
+
localSessionId: string,
|
|
660
|
+
requestedTitle: string | undefined,
|
|
661
|
+
userOpenId: string | undefined,
|
|
662
|
+
requestedProjectCwd?: string,
|
|
663
|
+
forceProjectless = false,
|
|
664
|
+
requestedAgentName?: string,
|
|
665
|
+
): Promise<ControlTaskGroupResult> {
|
|
666
|
+
const source = this.store.getSession(localSessionId);
|
|
667
|
+
if (!source || source.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
|
|
668
|
+
const agentName = requestedAgentName?.trim() || source.agentName;
|
|
669
|
+
const agent = this.ensureAgent(agentName);
|
|
670
|
+
if (forceProjectless && agent.kind !== "app-server") {
|
|
671
|
+
throw new Error("task newgroup --nodir is only available for Codex agents.");
|
|
672
|
+
}
|
|
673
|
+
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
674
|
+
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
675
|
+
const boundProjectCwd = forceProjectless
|
|
676
|
+
? undefined
|
|
677
|
+
: requestedProjectCwd === undefined
|
|
678
|
+
? detectProjectlessWorkspace(source.cwd) ? undefined : source.cwd
|
|
679
|
+
: resolveUserPath(requestedProjectCwd);
|
|
680
|
+
const created = await this.outbound.withReplyTarget(sourceContextKey, replyTarget, () =>
|
|
681
|
+
this.createFeishuGroupWithTask(
|
|
682
|
+
sourceContextKey,
|
|
683
|
+
agentName,
|
|
684
|
+
requestedTitle,
|
|
685
|
+
userOpenId,
|
|
686
|
+
boundProjectCwd,
|
|
687
|
+
source.agentName === agentName
|
|
688
|
+
? {
|
|
689
|
+
modelProvider: source.modelProvider,
|
|
690
|
+
model: source.model,
|
|
691
|
+
reasoningEffort: source.reasoningEffort,
|
|
692
|
+
permissionMode: source.permissionMode,
|
|
693
|
+
}
|
|
694
|
+
: {},
|
|
695
|
+
));
|
|
696
|
+
return {
|
|
697
|
+
sourceLocalSessionId: source.localSessionId,
|
|
698
|
+
group: created.group,
|
|
699
|
+
task: created.task,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async controlForkTaskGroup(
|
|
704
|
+
localSessionId: string,
|
|
705
|
+
requestedTitle: string | undefined,
|
|
706
|
+
userOpenId: string | undefined,
|
|
707
|
+
): Promise<ControlTaskGroupResult> {
|
|
708
|
+
const source = this.store.getSession(localSessionId);
|
|
709
|
+
if (!source || source.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
|
|
710
|
+
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
711
|
+
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
712
|
+
const plan = await this.prepareForkSession(sourceContextKey, localSessionId, requestedTitle);
|
|
713
|
+
const prepared: ForkGroupSessionPlan = {
|
|
714
|
+
plan,
|
|
715
|
+
sourceDescription: plan.forkedFromHistoricalTurn
|
|
716
|
+
? "当前任务最近已完成轮次"
|
|
717
|
+
: "当前任务最新轮次",
|
|
718
|
+
};
|
|
719
|
+
const forked = await this.outbound.withReplyTarget(sourceContextKey, replyTarget, () =>
|
|
720
|
+
this.forkPreparedSessionToFeishuGroup(
|
|
721
|
+
sourceContextKey,
|
|
722
|
+
prepared,
|
|
723
|
+
userOpenId ?? "",
|
|
724
|
+
"当前任务",
|
|
725
|
+
));
|
|
726
|
+
return {
|
|
727
|
+
sourceLocalSessionId: source.localSessionId,
|
|
728
|
+
sourceTurnId: plan.lastTurnId,
|
|
729
|
+
group: forked.group,
|
|
730
|
+
task: forked.task,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
644
734
|
private cardActionContextKey(action: CardAction): string {
|
|
645
735
|
const explicit = typeof action.value.contextKey === "string" ? action.value.contextKey : undefined;
|
|
646
736
|
if (explicit && baseChatContextKey(explicit) === baseChatContextKey(action.contextKey)) return explicit;
|
|
@@ -676,8 +766,8 @@ export class ProxySessionController {
|
|
|
676
766
|
await this.runShellCommand(contextKey, command.command);
|
|
677
767
|
return;
|
|
678
768
|
case "new":
|
|
679
|
-
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "
|
|
680
|
-
throw new Error("/new --nodir 仅支持
|
|
769
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
770
|
+
throw new Error("/new --nodir 仅支持 App Server Agent。");
|
|
681
771
|
}
|
|
682
772
|
await this.createSession(
|
|
683
773
|
contextKey,
|
|
@@ -696,8 +786,8 @@ export class ProxySessionController {
|
|
|
696
786
|
);
|
|
697
787
|
return;
|
|
698
788
|
case "newgroup":
|
|
699
|
-
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "
|
|
700
|
-
throw new Error("/newgroup --nodir 仅支持
|
|
789
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
790
|
+
throw new Error("/newgroup --nodir 仅支持 App Server Agent。");
|
|
701
791
|
}
|
|
702
792
|
await this.createFeishuGroup(
|
|
703
793
|
contextKey,
|
|
@@ -772,7 +862,13 @@ export class ProxySessionController {
|
|
|
772
862
|
const turnContextKey = session.lastTurnId
|
|
773
863
|
? this.store.getTurnContextKey(session.lastTurnId) ?? context.contextKey
|
|
774
864
|
: context.contextKey;
|
|
775
|
-
this.outbound.registerSession(
|
|
865
|
+
this.outbound.registerSession(
|
|
866
|
+
session.localSessionId,
|
|
867
|
+
turnContextKey,
|
|
868
|
+
session.title,
|
|
869
|
+
session.cwd,
|
|
870
|
+
this.agentLabel(session.agentName),
|
|
871
|
+
);
|
|
776
872
|
if (!session.lastTurnId) continue;
|
|
777
873
|
void this.outbound.resumeDelivery(session.localSessionId, turnContextKey, session.lastTurnId).catch((error: unknown) => {
|
|
778
874
|
this.logger.warn({ error, sessionId: session.localSessionId }, "Failed to restore persisted turn delivery.");
|
|
@@ -838,7 +934,13 @@ export class ProxySessionController {
|
|
|
838
934
|
): Promise<void> {
|
|
839
935
|
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
840
936
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
841
|
-
this.outbound.registerSession(
|
|
937
|
+
this.outbound.registerSession(
|
|
938
|
+
record.localSessionId,
|
|
939
|
+
contextKey,
|
|
940
|
+
record.title,
|
|
941
|
+
record.cwd,
|
|
942
|
+
this.agentLabel(record.agentName),
|
|
943
|
+
);
|
|
842
944
|
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, text);
|
|
843
945
|
}
|
|
844
946
|
let loaded: LoadedSession;
|
|
@@ -1098,7 +1200,13 @@ export class ProxySessionController {
|
|
|
1098
1200
|
permissionMode: source.permissionMode ?? "auto",
|
|
1099
1201
|
});
|
|
1100
1202
|
this.store.setCurrentSession(message.contextKey, localSessionId);
|
|
1101
|
-
this.outbound.registerSession(
|
|
1203
|
+
this.outbound.registerSession(
|
|
1204
|
+
localSessionId,
|
|
1205
|
+
message.contextKey,
|
|
1206
|
+
forkTitle,
|
|
1207
|
+
source.cwd,
|
|
1208
|
+
this.agentLabel(source.agentName),
|
|
1209
|
+
);
|
|
1102
1210
|
|
|
1103
1211
|
try {
|
|
1104
1212
|
const forked = await runtime.forkSession({
|
|
@@ -1411,7 +1519,13 @@ export class ProxySessionController {
|
|
|
1411
1519
|
lastTurnStatus: plan.lastTurnStatus,
|
|
1412
1520
|
});
|
|
1413
1521
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1414
|
-
this.outbound.registerSession(
|
|
1522
|
+
this.outbound.registerSession(
|
|
1523
|
+
localSessionId,
|
|
1524
|
+
contextKey,
|
|
1525
|
+
forked.title ?? plan.forkTitle,
|
|
1526
|
+
plan.cwd,
|
|
1527
|
+
this.agentLabel(plan.agentName),
|
|
1528
|
+
);
|
|
1415
1529
|
this.store.audit(contextKey, "session_forked", {
|
|
1416
1530
|
sourceLocalSessionId: plan.source?.localSessionId,
|
|
1417
1531
|
sourceRemoteSessionId: plan.remoteSessionId,
|
|
@@ -1566,7 +1680,7 @@ export class ProxySessionController {
|
|
|
1566
1680
|
const agent = this.ensureAgent(agentName);
|
|
1567
1681
|
const localSessionId = createId("sess");
|
|
1568
1682
|
const initialTitle = normalizeTaskTitle(requestedTitle ?? prompt ?? "");
|
|
1569
|
-
const sessionCwd = cwd === undefined && agent.kind === "
|
|
1683
|
+
const sessionCwd = cwd === undefined && agent.kind === "app-server"
|
|
1570
1684
|
? createProjectlessWorkspace({ prompt: initialTitle }).cwd
|
|
1571
1685
|
: path.resolve(cwd ?? this.config.defaults.cwd);
|
|
1572
1686
|
const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
|
|
@@ -1580,7 +1694,13 @@ export class ProxySessionController {
|
|
|
1580
1694
|
});
|
|
1581
1695
|
}
|
|
1582
1696
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1583
|
-
this.outbound.registerSession(
|
|
1697
|
+
this.outbound.registerSession(
|
|
1698
|
+
localSessionId,
|
|
1699
|
+
contextKey,
|
|
1700
|
+
initialTitle,
|
|
1701
|
+
sessionCwd,
|
|
1702
|
+
this.agentLabel(agentName),
|
|
1703
|
+
);
|
|
1584
1704
|
const runtime = this.runtimes.forAgent(agentName);
|
|
1585
1705
|
try {
|
|
1586
1706
|
if (prepareTurn) {
|
|
@@ -1666,9 +1786,9 @@ export class ProxySessionController {
|
|
|
1666
1786
|
userId: string | undefined,
|
|
1667
1787
|
boundProjectCwd: string | undefined,
|
|
1668
1788
|
executionSettings: SessionExecutionSettings,
|
|
1669
|
-
): Promise<
|
|
1789
|
+
): Promise<CreatedFeishuTaskGroup> {
|
|
1670
1790
|
const explicitTitle = normalizeTaskTitle(requestedTitle);
|
|
1671
|
-
const taskTitle = explicitTitle ??
|
|
1791
|
+
const taskTitle = explicitTitle ?? `新任务 (${formatGroupDateSuffix(new Date())})`;
|
|
1672
1792
|
const group = await this.createFeishuGroupContext(
|
|
1673
1793
|
sourceContextKey,
|
|
1674
1794
|
agentName,
|
|
@@ -1676,7 +1796,7 @@ export class ProxySessionController {
|
|
|
1676
1796
|
userId,
|
|
1677
1797
|
boundProjectCwd,
|
|
1678
1798
|
"/newgroup",
|
|
1679
|
-
|
|
1799
|
+
false,
|
|
1680
1800
|
);
|
|
1681
1801
|
|
|
1682
1802
|
let task: SessionRecord;
|
|
@@ -1719,6 +1839,7 @@ export class ProxySessionController {
|
|
|
1719
1839
|
sourceContextKey,
|
|
1720
1840
|
`已创建飞书群:${group.name},并创建新任务 ${taskDescription}。`,
|
|
1721
1841
|
);
|
|
1842
|
+
return { group, task };
|
|
1722
1843
|
}
|
|
1723
1844
|
|
|
1724
1845
|
private async forkCurrentSessionToFeishuGroup(
|
|
@@ -1766,7 +1887,7 @@ export class ProxySessionController {
|
|
|
1766
1887
|
prepared: ForkGroupSessionPlan,
|
|
1767
1888
|
userId: string,
|
|
1768
1889
|
sourceSummary: string,
|
|
1769
|
-
): Promise<
|
|
1890
|
+
): Promise<CreatedFeishuTaskGroup> {
|
|
1770
1891
|
const { plan } = prepared;
|
|
1771
1892
|
const boundProjectCwd = detectProjectlessWorkspace(plan.cwd) ? undefined : plan.cwd;
|
|
1772
1893
|
const group = await this.createFeishuGroupContext(
|
|
@@ -1809,6 +1930,7 @@ export class ProxySessionController {
|
|
|
1809
1930
|
sourceContextKey,
|
|
1810
1931
|
`已将${sourceSummary} Fork 到飞书群:${group.name};新群当前任务为 ${taskDescription}。`,
|
|
1811
1932
|
);
|
|
1933
|
+
return { group, task: forked.record };
|
|
1812
1934
|
}
|
|
1813
1935
|
|
|
1814
1936
|
private async createFeishuGroupContext(
|
|
@@ -1836,7 +1958,7 @@ export class ProxySessionController {
|
|
|
1836
1958
|
this.store.recordChatContext(groupContextKey, "group");
|
|
1837
1959
|
this.store.getOrCreateUserContext(groupContextKey, agentName);
|
|
1838
1960
|
if (boundProjectCwd) this.store.setBoundProjectCwd(groupContextKey, boundProjectCwd);
|
|
1839
|
-
return { contextKey: groupContextKey, name: group.name };
|
|
1961
|
+
return { chatId: group.chatId, contextKey: groupContextKey, name: group.name };
|
|
1840
1962
|
}
|
|
1841
1963
|
|
|
1842
1964
|
private async loadSession(record: SessionRecord): Promise<LoadedSession> {
|
|
@@ -1847,7 +1969,13 @@ export class ProxySessionController {
|
|
|
1847
1969
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
1848
1970
|
if (pending) return pending;
|
|
1849
1971
|
|
|
1850
|
-
this.outbound.registerSession(
|
|
1972
|
+
this.outbound.registerSession(
|
|
1973
|
+
record.localSessionId,
|
|
1974
|
+
record.contextKey,
|
|
1975
|
+
record.title,
|
|
1976
|
+
record.cwd,
|
|
1977
|
+
this.agentLabel(record.agentName),
|
|
1978
|
+
);
|
|
1851
1979
|
const loading = (async (): Promise<LoadedSession> => {
|
|
1852
1980
|
if (record.lastTurnId) {
|
|
1853
1981
|
try {
|
|
@@ -1874,14 +2002,14 @@ export class ProxySessionController {
|
|
|
1874
2002
|
model: record.model,
|
|
1875
2003
|
reasoningEffort: record.reasoningEffort,
|
|
1876
2004
|
permissionMode,
|
|
1877
|
-
activeTurnId: agent.kind === "
|
|
2005
|
+
activeTurnId: agent.kind === "app-server" && record.status === "running" && record.lastTurnStatus === "running"
|
|
1878
2006
|
? record.lastTurnId
|
|
1879
2007
|
: undefined,
|
|
1880
2008
|
lastTurnId: record.lastTurnId,
|
|
1881
2009
|
lastTurnStatus: record.lastTurnStatus,
|
|
1882
2010
|
});
|
|
1883
2011
|
} catch (error) {
|
|
1884
|
-
if (!(agent.kind === "
|
|
2012
|
+
if (!(agent.kind === "app-server" && !record.lastTurnId && isMissingRolloutError(error))) throw error;
|
|
1885
2013
|
this.logger.warn({ error, sessionId: record.localSessionId }, "Codex task has no rollout; creating a replacement task.");
|
|
1886
2014
|
session = await runtime.createSession({
|
|
1887
2015
|
localSessionId: record.localSessionId,
|
|
@@ -2480,7 +2608,7 @@ export class ProxySessionController {
|
|
|
2480
2608
|
}
|
|
2481
2609
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2482
2610
|
const agent = this.ensureAgent(context.defaultAgent);
|
|
2483
|
-
if (agent.kind !== "
|
|
2611
|
+
if (agent.kind !== "app-server") throw new Error("Goal 模式仅支持 App Server 任务。");
|
|
2484
2612
|
record = await this.createSession(
|
|
2485
2613
|
contextKey,
|
|
2486
2614
|
context.defaultAgent,
|
|
@@ -2919,7 +3047,13 @@ export class ProxySessionController {
|
|
|
2919
3047
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
2920
3048
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
2921
3049
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
2922
|
-
this.outbound.registerSession(
|
|
3050
|
+
this.outbound.registerSession(
|
|
3051
|
+
existing.localSessionId,
|
|
3052
|
+
contextKey,
|
|
3053
|
+
existing.title,
|
|
3054
|
+
existing.cwd,
|
|
3055
|
+
this.agentLabel(existing.agentName),
|
|
3056
|
+
);
|
|
2923
3057
|
await this.outbound.sendText(contextKey, `已切换到任务:${existing.title ?? existing.remoteSessionId ?? taskId}`);
|
|
2924
3058
|
return;
|
|
2925
3059
|
}
|
|
@@ -2946,7 +3080,13 @@ export class ProxySessionController {
|
|
|
2946
3080
|
lastTurnStatus: mapRemoteTurnStatus(remote.lastTurnStatus),
|
|
2947
3081
|
});
|
|
2948
3082
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
2949
|
-
this.outbound.registerSession(
|
|
3083
|
+
this.outbound.registerSession(
|
|
3084
|
+
localSessionId,
|
|
3085
|
+
contextKey,
|
|
3086
|
+
remote.title ?? remote.preview,
|
|
3087
|
+
remote.cwd,
|
|
3088
|
+
this.agentLabel(agentName),
|
|
3089
|
+
);
|
|
2950
3090
|
await this.outbound.sendText(
|
|
2951
3091
|
contextKey,
|
|
2952
3092
|
`已切换到任务:${remote.title ?? remote.preview ?? remote.id}。历史消息不会重新发送。`,
|
|
@@ -3320,9 +3460,13 @@ export class ProxySessionController {
|
|
|
3320
3460
|
return agent;
|
|
3321
3461
|
}
|
|
3322
3462
|
|
|
3463
|
+
private agentLabel(agentName: string): string {
|
|
3464
|
+
return this.ensureAgent(agentName).title;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3323
3467
|
private isCodexSession(session: SessionRecord): boolean {
|
|
3324
3468
|
if (session.runtimeKind) return session.runtimeKind === "codex";
|
|
3325
|
-
return this.config.agents[session.agentName]?.kind === "
|
|
3469
|
+
return this.config.agents[session.agentName]?.kind === "app-server";
|
|
3326
3470
|
}
|
|
3327
3471
|
|
|
3328
3472
|
private currentSession(contextKey: string): SessionRecord | undefined {
|
|
@@ -12,7 +12,7 @@ export function createAgentRuntimeRegistry(config: AppConfig, logger: Logger): A
|
|
|
12
12
|
const runtimes: Record<string, AgentRuntime> = {};
|
|
13
13
|
for (const [agentName, agent] of Object.entries(config.agents)) {
|
|
14
14
|
const runtimeLogger = logger.child({ agentName, runtimeKind: agent.kind });
|
|
15
|
-
if (agent.kind === "
|
|
15
|
+
if (agent.kind === "app-server") {
|
|
16
16
|
const processManager = new CodexProcessManager(agent.command, agent.args, agent.env, runtimeLogger);
|
|
17
17
|
runtimes[agentName] = new CodexRuntime(processManager, runtimeLogger);
|
|
18
18
|
continue;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface WindowsEnvironmentSnapshot {
|
|
5
|
+
machine: Record<string, string>;
|
|
6
|
+
user: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface EnvironmentRefreshResult {
|
|
10
|
+
environment: NodeJS.ProcessEnv;
|
|
11
|
+
refreshed: boolean;
|
|
12
|
+
pathChanged: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type WindowsEnvironmentLoader = () => WindowsEnvironmentSnapshot;
|
|
17
|
+
|
|
18
|
+
const PROCESS_LOCAL_ENVIRONMENT_PREFIXES = ["AGENT_BOT_", "FEISHU_"];
|
|
19
|
+
|
|
20
|
+
export function refreshedSystemEnvironment(
|
|
21
|
+
inherited: NodeJS.ProcessEnv = process.env,
|
|
22
|
+
platform: NodeJS.Platform = process.platform,
|
|
23
|
+
loadWindowsEnvironment: WindowsEnvironmentLoader = readWindowsEnvironment,
|
|
24
|
+
): EnvironmentRefreshResult {
|
|
25
|
+
const fallback = { ...inherited };
|
|
26
|
+
if (platform !== "win32") {
|
|
27
|
+
return { environment: fallback, refreshed: false, pathChanged: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const snapshot = loadWindowsEnvironment();
|
|
32
|
+
const environment = mergeWindowsEnvironment(inherited, snapshot);
|
|
33
|
+
return {
|
|
34
|
+
environment,
|
|
35
|
+
refreshed: true,
|
|
36
|
+
pathChanged: environmentValue(environment, "Path") !== environmentValue(inherited, "Path"),
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
environment: fallback,
|
|
41
|
+
refreshed: false,
|
|
42
|
+
pathChanged: false,
|
|
43
|
+
error: error instanceof Error ? error.message : String(error),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function mergeWindowsEnvironment(
|
|
49
|
+
inherited: NodeJS.ProcessEnv,
|
|
50
|
+
snapshot: WindowsEnvironmentSnapshot,
|
|
51
|
+
): NodeJS.ProcessEnv {
|
|
52
|
+
const environment = { ...inherited };
|
|
53
|
+
applyEnvironment(environment, snapshot.machine);
|
|
54
|
+
applyEnvironment(environment, snapshot.user);
|
|
55
|
+
|
|
56
|
+
const machinePath = environmentValue(snapshot.machine, "Path")?.trim();
|
|
57
|
+
const userPath = environmentValue(snapshot.user, "Path")?.trim();
|
|
58
|
+
if (machinePath || userPath) {
|
|
59
|
+
const inheritedPath = environmentValue(inherited, "Path")?.trim();
|
|
60
|
+
setEnvironmentValue(
|
|
61
|
+
environment,
|
|
62
|
+
"Path",
|
|
63
|
+
mergePathValues(
|
|
64
|
+
expandEnvironmentReferences(
|
|
65
|
+
[machinePath, userPath, inheritedPath].filter(Boolean).join(";"),
|
|
66
|
+
environment,
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return environment;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readWindowsEnvironment(): WindowsEnvironmentSnapshot {
|
|
75
|
+
const systemRoot = process.env.SystemRoot ?? "C:\\Windows";
|
|
76
|
+
const executable = path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
77
|
+
const script = [
|
|
78
|
+
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
|
|
79
|
+
"$machine = @{}",
|
|
80
|
+
"foreach ($entry in [Environment]::GetEnvironmentVariables([EnvironmentVariableTarget]::Machine).GetEnumerator()) { $machine[[string]$entry.Key] = [string]$entry.Value }",
|
|
81
|
+
"$user = @{}",
|
|
82
|
+
"foreach ($entry in [Environment]::GetEnvironmentVariables([EnvironmentVariableTarget]::User).GetEnumerator()) { $user[[string]$entry.Key] = [string]$entry.Value }",
|
|
83
|
+
"[pscustomobject]@{ machine = $machine; user = $user } | ConvertTo-Json -Compress -Depth 3",
|
|
84
|
+
].join("; ");
|
|
85
|
+
const result = spawnSync(executable, [
|
|
86
|
+
"-NoLogo",
|
|
87
|
+
"-NoProfile",
|
|
88
|
+
"-NonInteractive",
|
|
89
|
+
"-ExecutionPolicy",
|
|
90
|
+
"Bypass",
|
|
91
|
+
"-Command",
|
|
92
|
+
script,
|
|
93
|
+
], {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
timeout: 5_000,
|
|
97
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
98
|
+
});
|
|
99
|
+
if (result.error) throw result.error;
|
|
100
|
+
if (result.status !== 0) {
|
|
101
|
+
throw new Error(result.stderr.trim() || `PowerShell exited with code ${result.status ?? "unknown"}.`);
|
|
102
|
+
}
|
|
103
|
+
const parsed = JSON.parse(result.stdout) as Partial<WindowsEnvironmentSnapshot>;
|
|
104
|
+
return {
|
|
105
|
+
machine: environmentRecord(parsed.machine),
|
|
106
|
+
user: environmentRecord(parsed.user),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function environmentRecord(value: unknown): Record<string, string> {
|
|
111
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
112
|
+
return Object.fromEntries(
|
|
113
|
+
Object.entries(value).flatMap(([name, entry]) =>
|
|
114
|
+
typeof entry === "string" ? [[name, entry]] : []),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function applyEnvironment(
|
|
119
|
+
target: NodeJS.ProcessEnv,
|
|
120
|
+
source: Record<string, string>,
|
|
121
|
+
): void {
|
|
122
|
+
for (const [name, value] of Object.entries(source)) {
|
|
123
|
+
if (name.toLowerCase() === "path" || isProcessLocalEnvironmentName(name)) continue;
|
|
124
|
+
setEnvironmentValue(target, name, value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isProcessLocalEnvironmentName(name: string): boolean {
|
|
129
|
+
const normalized = name.toUpperCase();
|
|
130
|
+
return PROCESS_LOCAL_ENVIRONMENT_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setEnvironmentValue(environment: NodeJS.ProcessEnv, name: string, value: string): void {
|
|
134
|
+
for (const existingName of Object.keys(environment)) {
|
|
135
|
+
if (existingName !== name && existingName.toLowerCase() === name.toLowerCase()) {
|
|
136
|
+
delete environment[existingName];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
environment[name] = value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function environmentValue(
|
|
143
|
+
environment: NodeJS.ProcessEnv | Record<string, string>,
|
|
144
|
+
name: string,
|
|
145
|
+
): string | undefined {
|
|
146
|
+
const key = Object.keys(environment).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
147
|
+
return key === undefined ? undefined : environment[key];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function expandEnvironmentReferences(value: string, environment: NodeJS.ProcessEnv): string {
|
|
151
|
+
let expanded = value;
|
|
152
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
153
|
+
const next = expanded.replace(/%([^%]+)%/g, (match, name: string) =>
|
|
154
|
+
environmentValue(environment, name) ?? match);
|
|
155
|
+
if (next === expanded) break;
|
|
156
|
+
expanded = next;
|
|
157
|
+
}
|
|
158
|
+
return expanded;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function mergePathValues(value: string): string {
|
|
162
|
+
const seen = new Set<string>();
|
|
163
|
+
return value
|
|
164
|
+
.split(";")
|
|
165
|
+
.map((entry) => entry.trim())
|
|
166
|
+
.filter((entry) => {
|
|
167
|
+
if (!entry) return false;
|
|
168
|
+
const normalized = entry.replace(/[\\/]+$/, "").toLowerCase();
|
|
169
|
+
if (seen.has(normalized)) return false;
|
|
170
|
+
seen.add(normalized);
|
|
171
|
+
return true;
|
|
172
|
+
})
|
|
173
|
+
.join(";");
|
|
174
|
+
}
|
package/src/supervisor.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
STOP_EXIT_CODE,
|
|
14
14
|
} from "./supervision/restartPolicy.js";
|
|
15
15
|
import { SupervisorDiagnostics, nodeDiagnosticReportArguments } from "./supervision/SupervisorDiagnostics.js";
|
|
16
|
+
import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
|
|
16
17
|
|
|
17
18
|
const childEntry = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
18
19
|
const config = loadConfig();
|
|
@@ -38,13 +39,23 @@ async function startChild(): Promise<void> {
|
|
|
38
39
|
const restartReason = nextStartReason;
|
|
39
40
|
nextStartReason = "Supervisor 重新拉起进程";
|
|
40
41
|
const workerStderr = diagnostics.openWorkerStderr();
|
|
42
|
+
const environmentRefresh = refreshedSystemEnvironment();
|
|
43
|
+
if (environmentRefresh.error) {
|
|
44
|
+
writeSupervisorLog("environment_refresh_failed", { error: environmentRefresh.error });
|
|
45
|
+
} else if (environmentRefresh.refreshed) {
|
|
46
|
+
writeSupervisorLog("environment_refreshed", { pathChanged: environmentRefresh.pathChanged });
|
|
47
|
+
}
|
|
41
48
|
try {
|
|
42
49
|
child = spawn(process.execPath, [
|
|
43
50
|
...nodeDiagnosticReportArguments(diagnostics.paths.crashReportDirectory),
|
|
44
51
|
childEntry,
|
|
45
52
|
], {
|
|
46
53
|
cwd: process.cwd(),
|
|
47
|
-
env: {
|
|
54
|
+
env: {
|
|
55
|
+
...environmentRefresh.environment,
|
|
56
|
+
AGENT_BOT_SUPERVISED: "1",
|
|
57
|
+
AGENT_BOT_RESTART_REASON: restartReason,
|
|
58
|
+
},
|
|
48
59
|
stdio: ["ignore", "ignore", workerStderr],
|
|
49
60
|
windowsHide: true,
|
|
50
61
|
});
|