@dorokuma/herdsman-pi 0.14.9 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorokuma/herdsman-pi",
3
- "version": "0.14.9",
3
+ "version": "0.15.0",
4
4
  "description": "Pi extension bridge for Herdsman agent history.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -21,6 +21,9 @@
21
21
  "url": "git+https://github.com/dorokuma/herdsman.git",
22
22
  "directory": "packages/herdsman-pi"
23
23
  },
24
+ "scripts": {
25
+ "typecheck": "tsc --noEmit -p tsconfig.json"
26
+ },
24
27
  "pi": {
25
28
  "extensions": [
26
29
  "./src/index.ts"
@@ -34,8 +37,5 @@
34
37
  "bugs": {
35
38
  "url": "https://github.com/dorokuma/herdsman/issues"
36
39
  },
37
- "homepage": "https://github.com/dorokuma/herdsman#readme",
38
- "scripts": {
39
- "typecheck": "tsc --noEmit -p tsconfig.json"
40
- }
41
- }
40
+ "homepage": "https://github.com/dorokuma/herdsman#readme"
41
+ }
package/src/index.ts CHANGED
@@ -97,6 +97,7 @@ type DeliveredBatch = {
97
97
  };
98
98
 
99
99
  type HerdsmanState = {
100
+ autoClaimAttempted: boolean;
100
101
  client: HerdsmanDaemonClient | undefined;
101
102
  connected: boolean;
102
103
  currentScope: CurrentScope | undefined;
@@ -450,6 +451,7 @@ export function createHerdsmanPiExtension(options: ExtensionOptions = {}) {
450
451
  const wakeFilter = options.wakeFilter ?? loadWakeFilterConfig();
451
452
 
452
453
  const state: HerdsmanState = {
454
+ autoClaimAttempted: false,
453
455
  client: undefined,
454
456
  connected: false,
455
457
  currentScope: undefined,
@@ -2021,10 +2023,32 @@ export function createHerdsmanPiExtension(options: ExtensionOptions = {}) {
2021
2023
  subscriberKind: "pi",
2022
2024
  workspaceId: state.currentScope?.workspaceId ?? launchIdentity.workspaceId,
2023
2025
  })
2024
- .then((response) => {
2026
+ .then(async (rawResponse) => {
2025
2027
  state.connected = true;
2026
2028
  startKeepalive(client);
2027
- applyConnectionStateResponse(response as ConnectionStateResponse, ctx, {
2029
+ let response = rawResponse as ConnectionStateResponse;
2030
+ const currentPaneId = state.currentScope?.paneId ?? launchIdentity.paneId;
2031
+ if (
2032
+ process.env.HERDSMAN_AUTO_ON === "1" &&
2033
+ !state.autoClaimAttempted &&
2034
+ isMainOrchestratorPane(currentPaneId) &&
2035
+ !isDispatchedRoleSession(sessionRef.value, process.env)
2036
+ ) {
2037
+ state.autoClaimAttempted = true;
2038
+ if (!isLocalOwner(response) && !response.state?.owner) {
2039
+ try {
2040
+ state.roleMutationInFlight = true;
2041
+ response = (await client.request("agent.orchestrator.set", {
2042
+ enabled: true,
2043
+ })) as ConnectionStateResponse;
2044
+ } catch {
2045
+ // Ignore race if another terminal claimed the workspace concurrently.
2046
+ } finally {
2047
+ state.roleMutationInFlight = false;
2048
+ }
2049
+ }
2050
+ }
2051
+ applyConnectionStateResponse(response, ctx, {
2028
2052
  notifyReconnectLoss: true,
2029
2053
  });
2030
2054
  })
@@ -2095,6 +2119,7 @@ export function createHerdsmanPiExtension(options: ExtensionOptions = {}) {
2095
2119
 
2096
2120
  pi.on("session_start", (_event: unknown, ctx: PiContext) => {
2097
2121
  activeContext = ctx;
2122
+ state.autoClaimAttempted = false;
2098
2123
  state.subscriberId = ctx.sessionManager.getSessionId();
2099
2124
  state.sessionRef = {
2100
2125
  agent: "pi",
@@ -2438,6 +2463,17 @@ function herdrLaunchIdentity(environment: NodeJS.ProcessEnv): LaunchIdentity | u
2438
2463
  return { herdrSocketPath, paneId, workspaceId };
2439
2464
  }
2440
2465
 
2466
+ function isMainOrchestratorPane(paneId: string): boolean {
2467
+ return paneId.trim().endsWith(":p1");
2468
+ }
2469
+
2470
+ function isDispatchedRoleSession(sessionPath: string, environment: NodeJS.ProcessEnv): boolean {
2471
+ if (typeof environment.CTXMODE_ROLE === "string" && environment.CTXMODE_ROLE.trim().length > 0) {
2472
+ return true;
2473
+ }
2474
+ return sessionPath.startsWith("/tmp/herdr-role-sessions/");
2475
+ }
2476
+
2441
2477
 
2442
2478
  function stringValue(value: unknown): string | null {
2443
2479
  return typeof value === "string" && value.length > 0 ? value : null;
package/src/wake.ts CHANGED
@@ -11,6 +11,7 @@ export const WAKE_SETTLE_MS = 0;
11
11
  // value is the contract; duplicated here because `@dorokuma/herdsman-pi` ships
12
12
  // standalone `src/` without root `src/`).
13
13
  export const PLAN_WAITING_HISTORY = "PLAN_WAITING_HISTORY";
14
+ export const PLAN_STALLED = "PLAN_STALLED";
14
15
 
15
16
  export type AgentOutcome = {
16
17
  agent: string;
@@ -93,8 +94,18 @@ function outcomeKind(event: AgentEventWireRecord): AgentOutcome["kind"] | undefi
93
94
  return "failed";
94
95
  }
95
96
  if (event.type === "agent.discarded") {
96
- // 观察者放弃等待也是终态失败:结果永远不会到达,必须能在编排者对话里被唤醒。
97
- // 压制/死信语义保持既有实现(上游错误抑制、pane 级 fallback 压制、seen 去重)。
97
+ const payload = asRecord(event.payload);
98
+ const agent = stringValue(payload.agent);
99
+ const isAgyOrEmpty = agent === undefined || agent === "agy";
100
+ if (isAgyOrEmpty) {
101
+ const reason = stringValue(payload.reason);
102
+ if (reason === PLAN_WAITING_HISTORY || reason === "degraded") {
103
+ if (payload.fallbackOutcome === true) {
104
+ return "failed";
105
+ }
106
+ return undefined;
107
+ }
108
+ }
98
109
  return "failed";
99
110
  }
100
111
  const payload = asRecord(event.payload);