@parall/daemon 1.29.0 → 1.29.2

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/config.d.ts CHANGED
@@ -22,6 +22,8 @@ export type ClaudeDaemonConfig = {
22
22
  rootClaudeHome: string;
23
23
  /** Optional WS URL override for the machine control-plane WebSocket. */
24
24
  wsUrl?: string;
25
+ /** Optional swimlane routing name, e.g. pr-1191. */
26
+ swimlaneName?: string;
25
27
  /** @deprecated Superseded by WS event-driven model; kept for backward compat. */
26
28
  pollIntervalMs: number;
27
29
  /** @deprecated Superseded by WS event-driven model; kept for backward compat. */
@@ -61,5 +63,5 @@ export declare function sharedClaudeCredentialsFileFor(rootClaudeHome: string):
61
63
  export declare function agentClaudeCredentialsFileFor(agentClaudeHome: string): string;
62
64
  /** Per-agent workspace dir where the agent runs git commands. */
63
65
  export declare function agentWorkspaceDirFor(rootStateDir: string, agentId: string): string;
64
- export declare function resolveWsUrl(apiUrl: string, explicitWsUrl?: string): string;
66
+ export declare function resolveWsUrl(apiUrl: string, explicitWsUrl?: string, swimlaneName?: string): string;
65
67
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAAC;AA2BF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA4ClG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAAC;AA2BF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA6ClG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG"}
package/dist/config.js CHANGED
@@ -82,6 +82,7 @@ export function resolveClaudeDaemonConfig(env = process.env) {
82
82
  rootStateDir,
83
83
  rootClaudeHome,
84
84
  wsUrl: env.PRLL_WS_URL?.trim() || undefined,
85
+ swimlaneName: env.PRLL_SWIMLANE_NAME?.trim() || undefined,
85
86
  pollIntervalMs: parseMs(env.PRLL_DAEMON_POLL_INTERVAL_MS, 30_000),
86
87
  heartbeatIntervalMs: parseMs(env.PRLL_DAEMON_HEARTBEAT_INTERVAL_MS, 30_000),
87
88
  restartBackoffMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MS, 5_000),
@@ -120,6 +121,11 @@ export function agentClaudeCredentialsFileFor(agentClaudeHome) {
120
121
  export function agentWorkspaceDirFor(rootStateDir, agentId) {
121
122
  return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
122
123
  }
123
- export function resolveWsUrl(apiUrl, explicitWsUrl) {
124
- return explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
124
+ export function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
125
+ const base = explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
126
+ if (!swimlaneName)
127
+ return base;
128
+ const url = new URL(base);
129
+ url.searchParams.set("swimlane", swimlaneName);
130
+ return url.toString();
125
131
  }
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ async function main() {
76
76
  const client = new ParallClient({
77
77
  baseUrl: config.apiUrl,
78
78
  token: config.apiKey,
79
+ swimlaneName: config.swimlaneName,
79
80
  });
80
81
  const abortController = new AbortController();
81
82
  const onSignal = (sig) => {
@@ -95,7 +96,7 @@ async function main() {
95
96
  process.exitCode = 1;
96
97
  process.exit(1);
97
98
  });
98
- config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl);
99
+ config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
99
100
  await runForever(config, client, log, abortController.signal);
100
101
  }
101
102
  const cliArgs = process.argv.slice(2);
@@ -14,6 +14,7 @@ export interface AgentDirs {
14
14
  workspaceDir: string;
15
15
  claudeHome: string;
16
16
  }
17
+ export declare function clearAllProviderCreds(env: NodeJS.ProcessEnv): void;
17
18
  export declare function getRuntimeAdapter(runtimeType: string): RuntimeAdapter;
18
19
  export declare function assertAgentKey(apiKey: string): void;
19
20
  //# sourceMappingURL=runtimes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;CAC/I;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAsHD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAErE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAInD"}
1
+ {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;CAC/I;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAeD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAOlE;AAgGD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAErE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAInD"}
package/dist/runtimes.js CHANGED
@@ -10,7 +10,7 @@ function llmSource(pc) {
10
10
  }
11
11
  return "parall";
12
12
  }
13
- function clearAllProviderCreds(env) {
13
+ export function clearAllProviderCreds(env) {
14
14
  delete env.ANTHROPIC_AUTH_TOKEN;
15
15
  delete env.ANTHROPIC_BASE_URL;
16
16
  delete env.ANTHROPIC_API_KEY;
@@ -24,6 +24,10 @@ export declare class DaemonSupervisor {
24
24
  private readonly client;
25
25
  private readonly log;
26
26
  private readonly children;
27
+ private readonly spawningAgents;
28
+ private readonly pendingWorkspaceSetup;
29
+ private readonly workspaceSetupRetryTimers;
30
+ private readonly cancelledSpawns;
27
31
  private ws;
28
32
  private running;
29
33
  private machineOrgId;
@@ -42,12 +46,17 @@ export declare class DaemonSupervisor {
42
46
  * by parsing session state files which embed the agent ID in the runtimeKey.
43
47
  */
44
48
  private migrateFlatLayout;
49
+ private fetchAttachedAgent;
45
50
  private handleAgentAttached;
46
51
  private handleAgentDetached;
52
+ private handleWorkspaceSetupRequested;
53
+ private scheduleWorkspaceSetupRetry;
54
+ private clearWorkspaceSetupRetry;
47
55
  private refreshMachineConfig;
48
56
  private respawnAllChildren;
49
57
  private restartChildNow;
50
58
  private spawnAgent;
59
+ private spawnAgentOnce;
51
60
  private startChild;
52
61
  private terminateChild;
53
62
  private ensureSharedCredentialLink;
@@ -1 +1 @@
1
- {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAkL,MAAM,aAAa,CAAC;AAC3N,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AASrB;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAyB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IASzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,aAAa;IAGrC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA4E7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Bb,kBAAkB;YAqClB,aAAa;IAqD3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YA6CX,mBAAmB;YA6BnB,mBAAmB;YAgBnB,oBAAoB;YAcpB,kBAAkB;YAYlB,eAAe;YAcf,UAAU;IAgDxB,OAAO,CAAC,UAAU;YAyEJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAA2N,MAAM,aAAa,CAAC;AACpQ,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AAiBrB;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAyB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAazB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqC;IAC/E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,aAAa;IAGrC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiF7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAiCb,kBAAkB;YAqClB,aAAa;IAqD3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YA6CX,kBAAkB;YAqBlB,mBAAmB;YAgBnB,mBAAmB;YAmBnB,6BAA6B;IAqC3C,OAAO,CAAC,2BAA2B;IAYnC,OAAO,CAAC,wBAAwB;YAUlB,oBAAoB;YAcpB,kBAAkB;YAYlB,eAAe;YAcf,UAAU;YAoBV,cAAc;IA2D5B,OAAO,CAAC,UAAU;YA0EJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}
@@ -4,11 +4,13 @@ import * as path from "node:path";
4
4
  import { ParallWs } from "@parall/sdk";
5
5
  import { agentClaudeCredentialsFileFor, agentClaudeHomeFor, agentStateDirFor, agentWorkspaceDirFor, sharedClaudeCredentialsFileFor, } from "./config.js";
6
6
  import { assertAgentKey, getRuntimeAdapter } from "./runtimes.js";
7
+ import { prepareWorkspace } from "./workspace.js";
7
8
  const RUNTIME_PACKAGES = {
8
9
  'claude-code': '@parall/claude-agent',
9
10
  'codex': '@parall/codex-agent',
10
11
  'openclaw': '@parall/openclaw-agent',
11
12
  };
13
+ const WORKSPACE_SETUP_RETRY_DELAY_MS = 5_000;
12
14
  /**
13
15
  * Sleep that wakes early on abort. Returns true if the full delay elapsed,
14
16
  * false if aborted. Used by bootstrap retry and the outer keepalive in
@@ -46,6 +48,10 @@ export class DaemonSupervisor {
46
48
  client;
47
49
  log;
48
50
  children = new Map();
51
+ spawningAgents = new Set();
52
+ pendingWorkspaceSetup = new Set();
53
+ workspaceSetupRetryTimers = new Map();
54
+ cancelledSpawns = new Set();
49
55
  ws = null;
50
56
  running = false;
51
57
  machineOrgId = null;
@@ -107,6 +113,10 @@ export class DaemonSupervisor {
107
113
  void this.respawnAllChildren();
108
114
  }
109
115
  });
116
+ this.ws.on("machine.workspace.setup.requested", (data) => {
117
+ this.log.info(`WS: workspace setup requested for agent ${data.agent_id}`);
118
+ void this.handleWorkspaceSetupRequested(data.agent_id);
119
+ });
110
120
  this.ws.on("machine.stop", (data) => {
111
121
  this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
112
122
  void this.stop();
@@ -132,6 +142,10 @@ export class DaemonSupervisor {
132
142
  this.ws = null;
133
143
  }
134
144
  const exits = [];
145
+ for (const timer of this.workspaceSetupRetryTimers.values()) {
146
+ clearTimeout(timer);
147
+ }
148
+ this.workspaceSetupRetryTimers.clear();
135
149
  for (const state of this.children.values()) {
136
150
  state.shuttingDown = true;
137
151
  if (state.restartTimer) {
@@ -277,24 +291,31 @@ export class DaemonSupervisor {
277
291
  }
278
292
  }
279
293
  // ---- WS event handlers (incremental) ----
280
- async handleAgentAttached(agentId) {
281
- if (this.children.has(agentId))
282
- return;
294
+ async fetchAttachedAgent(agentId) {
283
295
  let attached;
284
296
  try {
285
297
  attached = await this.client.listAttachedAgents();
286
298
  }
287
299
  catch (err) {
288
- this.log.warn(`handleAgentAttached: listAttachedAgents failed: ${String(err)}`);
289
- return;
300
+ this.log.warn(`fetchAttachedAgent: listAttachedAgents failed: ${String(err)}`);
301
+ return { kind: "retryable" };
290
302
  }
291
303
  const entry = attached.find((a) => (a.user?.id ?? a.profile.user_id) === agentId);
292
304
  if (!entry) {
293
- this.log.warn(`handleAgentAttached: agent ${agentId} not found in attached list`);
294
- return;
305
+ this.log.warn(`fetchAttachedAgent: agent ${agentId} not found in attached list`);
306
+ return { kind: "skip" };
295
307
  }
296
308
  if (entry.user && entry.user.status !== "active") {
297
309
  this.log.info(`agent ${agentId} not active (status=${entry.user.status}) — skipping`);
310
+ return { kind: "skip" };
311
+ }
312
+ return { kind: "found", entry };
313
+ }
314
+ async handleAgentAttached(agentId) {
315
+ if (this.children.has(agentId) || this.spawningAgents.has(agentId))
316
+ return;
317
+ const result = await this.fetchAttachedAgent(agentId);
318
+ if (result.kind !== "found") {
298
319
  return;
299
320
  }
300
321
  const orgId = this.machineOrgId;
@@ -302,9 +323,14 @@ export class DaemonSupervisor {
302
323
  this.log.warn(`agent ${agentId}: no org_id available yet; skipping`);
303
324
  return;
304
325
  }
305
- await this.spawnAgent(agentId, orgId, entry);
326
+ await this.spawnAgent(agentId, orgId, result.entry);
306
327
  }
307
328
  async handleAgentDetached(agentId) {
329
+ this.pendingWorkspaceSetup.delete(agentId);
330
+ this.clearWorkspaceSetupRetry(agentId);
331
+ if (this.spawningAgents.has(agentId)) {
332
+ this.cancelledSpawns.add(agentId);
333
+ }
308
334
  const state = this.children.get(agentId);
309
335
  if (!state)
310
336
  return;
@@ -317,6 +343,61 @@ export class DaemonSupervisor {
317
343
  await this.terminateChild(state);
318
344
  this.children.delete(agentId);
319
345
  }
346
+ async handleWorkspaceSetupRequested(agentId) {
347
+ if (this.spawningAgents.has(agentId)) {
348
+ this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
349
+ this.pendingWorkspaceSetup.add(agentId);
350
+ return;
351
+ }
352
+ const result = await this.fetchAttachedAgent(agentId);
353
+ if (result.kind === "retryable") {
354
+ this.pendingWorkspaceSetup.add(agentId);
355
+ this.scheduleWorkspaceSetupRetry(agentId);
356
+ return;
357
+ }
358
+ if (result.kind === "skip") {
359
+ this.pendingWorkspaceSetup.delete(agentId);
360
+ this.clearWorkspaceSetupRetry(agentId);
361
+ return;
362
+ }
363
+ this.clearWorkspaceSetupRetry(agentId);
364
+ const state = this.children.get(agentId);
365
+ if (state) {
366
+ this.log.info(`agent ${agentId}: restarting for workspace setup`);
367
+ state.shuttingDown = true;
368
+ if (state.restartTimer) {
369
+ clearTimeout(state.restartTimer);
370
+ state.restartTimer = null;
371
+ }
372
+ await this.terminateChild(state);
373
+ this.children.delete(agentId);
374
+ }
375
+ const orgId = this.machineOrgId;
376
+ if (!orgId) {
377
+ this.log.warn(`agent ${agentId}: no org_id available yet; skipping`);
378
+ return;
379
+ }
380
+ await this.spawnAgent(agentId, orgId, result.entry);
381
+ }
382
+ scheduleWorkspaceSetupRetry(agentId) {
383
+ if (!this.running || this.workspaceSetupRetryTimers.has(agentId))
384
+ return;
385
+ const timer = setTimeout(() => {
386
+ this.workspaceSetupRetryTimers.delete(agentId);
387
+ if (this.running && this.pendingWorkspaceSetup.has(agentId)) {
388
+ void this.handleWorkspaceSetupRequested(agentId);
389
+ }
390
+ }, WORKSPACE_SETUP_RETRY_DELAY_MS);
391
+ timer.unref?.();
392
+ this.workspaceSetupRetryTimers.set(agentId, timer);
393
+ }
394
+ clearWorkspaceSetupRetry(agentId) {
395
+ const timer = this.workspaceSetupRetryTimers.get(agentId);
396
+ if (timer) {
397
+ clearTimeout(timer);
398
+ this.workspaceSetupRetryTimers.delete(agentId);
399
+ }
400
+ }
320
401
  // ---- Spawn / restart ----
321
402
  async refreshMachineConfig() {
322
403
  try {
@@ -358,6 +439,26 @@ export class DaemonSupervisor {
358
439
  }
359
440
  }
360
441
  async spawnAgent(agentId, orgId, attached) {
442
+ if (this.children.has(agentId) || this.spawningAgents.has(agentId)) {
443
+ return;
444
+ }
445
+ this.spawningAgents.add(agentId);
446
+ let shouldReplaySetupRequest = false;
447
+ try {
448
+ await this.spawnAgentOnce(agentId, orgId, attached);
449
+ }
450
+ finally {
451
+ this.spawningAgents.delete(agentId);
452
+ this.cancelledSpawns.delete(agentId);
453
+ shouldReplaySetupRequest = this.pendingWorkspaceSetup.delete(agentId);
454
+ }
455
+ if (shouldReplaySetupRequest && this.running) {
456
+ queueMicrotask(() => {
457
+ void this.handleWorkspaceSetupRequested(agentId);
458
+ });
459
+ }
460
+ }
461
+ async spawnAgentOnce(agentId, orgId, attached) {
361
462
  let credential;
362
463
  try {
363
464
  credential = await this.client.mintLaunchCredential(agentId);
@@ -367,19 +468,13 @@ export class DaemonSupervisor {
367
468
  return;
368
469
  }
369
470
  const stateDir = agentStateDirFor(this.config.rootStateDir, agentId);
370
- const workspaceDir = attached.daemon_config?.workspace_path
371
- || agentWorkspaceDirFor(this.config.rootStateDir, agentId);
471
+ const defaultWorkspaceDir = agentWorkspaceDirFor(this.config.rootStateDir, agentId);
372
472
  const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
373
473
  const claudeHome = isK8s
374
474
  ? agentClaudeHomeFor(this.config.rootClaudeHome, agentId)
375
475
  : this.config.rootClaudeHome;
376
476
  try {
377
477
  fs.mkdirSync(stateDir, { recursive: true });
378
- // Only create workspace dir when using the default isolated path —
379
- // user-specified workspace_path should already exist.
380
- if (!attached.daemon_config?.workspace_path) {
381
- fs.mkdirSync(workspaceDir, { recursive: true });
382
- }
383
478
  if (isK8s) {
384
479
  fs.mkdirSync(claudeHome, { recursive: true });
385
480
  this.ensureSharedCredentialLink(claudeHome, agentId);
@@ -389,6 +484,24 @@ export class DaemonSupervisor {
389
484
  this.log.warn(`mkdir agent dirs (${agentId}) failed: ${String(err)}`);
390
485
  }
391
486
  const runtimeType = attached.profile.runtime_type ?? "claude-code";
487
+ let workspaceDir;
488
+ try {
489
+ workspaceDir = await prepareWorkspace({
490
+ client: this.client,
491
+ attached,
492
+ agentId,
493
+ defaultWorkspaceDir,
494
+ log: this.log,
495
+ });
496
+ }
497
+ catch (err) {
498
+ this.log.error(`agent ${agentId}: workspace setup failed: ${String(err)}`);
499
+ return;
500
+ }
501
+ if (this.cancelledSpawns.delete(agentId)) {
502
+ this.log.info(`agent ${agentId}: spawn cancelled after detach during workspace setup`);
503
+ return;
504
+ }
392
505
  const state = {
393
506
  agentId,
394
507
  orgId,
@@ -460,11 +573,12 @@ export class DaemonSupervisor {
460
573
  settleChild("error", null, null, err);
461
574
  });
462
575
  child.once("close", (code, signal) => settleChild("close", code, signal));
463
- setTimeout(() => {
576
+ const stableTimer = setTimeout(() => {
464
577
  if (state.child === child) {
465
578
  state.restartAttempts = 0;
466
579
  }
467
580
  }, Math.max(this.config.restartBackoffMs, 30_000));
581
+ stableTimer.unref?.();
468
582
  }
469
583
  async terminateChild(state) {
470
584
  const child = state.child;
@@ -0,0 +1,11 @@
1
+ import type { GatewayLogger } from "@parall/agent-core";
2
+ import type { AttachedAgent, ParallClient } from "@parall/sdk";
3
+ export interface PrepareWorkspaceOpts {
4
+ client: ParallClient;
5
+ attached: AttachedAgent;
6
+ agentId: string;
7
+ defaultWorkspaceDir: string;
8
+ log: GatewayLogger;
9
+ }
10
+ export declare function prepareWorkspace(opts: PrepareWorkspaceOpts): Promise<string>;
11
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAA4C,YAAY,EAAE,MAAM,aAAa,CAAC;AAiBzG,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,aAAa,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ClF"}