@factiii/runner 0.9.3 → 0.9.5

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.
Files changed (3) hide show
  1. package/dist/cli.js +272 -57
  2. package/index/index.js +35 -108
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4537,7 +4537,7 @@ var require_permessage_deflate = __commonJS({
4537
4537
  acceptAsServer(offers) {
4538
4538
  const opts = this._options;
4539
4539
  const accepted = offers.find((params) => {
4540
- if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && !params.client_max_window_bits) {
4540
+ if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && (typeof params.client_max_window_bits === "number" ? opts.clientMaxWindowBits > params.client_max_window_bits : !params.client_max_window_bits)) {
4541
4541
  return false;
4542
4542
  }
4543
4543
  return true;
@@ -5057,6 +5057,7 @@ var require_receiver = __commonJS({
5057
5057
  this._opcode = 0;
5058
5058
  this._totalPayloadLength = 0;
5059
5059
  this._messageLength = 0;
5060
+ this._numFragments = 0;
5060
5061
  this._fragments = [];
5061
5062
  this._errored = false;
5062
5063
  this._loop = false;
@@ -5407,23 +5408,23 @@ var require_receiver = __commonJS({
5407
5408
  this.controlMessage(data, cb);
5408
5409
  return;
5409
5410
  }
5411
+ if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
5412
+ const error = this.createError(
5413
+ RangeError,
5414
+ "Too many message fragments",
5415
+ false,
5416
+ 1008,
5417
+ "WS_ERR_TOO_MANY_BUFFERED_PARTS"
5418
+ );
5419
+ cb(error);
5420
+ return;
5421
+ }
5410
5422
  if (this._compressed) {
5411
5423
  this._state = INFLATING;
5412
5424
  this.decompress(data, cb);
5413
5425
  return;
5414
5426
  }
5415
5427
  if (data.length) {
5416
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
5417
- const error = this.createError(
5418
- RangeError,
5419
- "Too many message fragments",
5420
- false,
5421
- 1008,
5422
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
5423
- );
5424
- cb(error);
5425
- return;
5426
- }
5427
5428
  this._messageLength = this._totalPayloadLength;
5428
5429
  this._fragments.push(data);
5429
5430
  }
@@ -5453,17 +5454,6 @@ var require_receiver = __commonJS({
5453
5454
  cb(error);
5454
5455
  return;
5455
5456
  }
5456
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
5457
- const error = this.createError(
5458
- RangeError,
5459
- "Too many message fragments",
5460
- false,
5461
- 1008,
5462
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
5463
- );
5464
- cb(error);
5465
- return;
5466
- }
5467
5457
  this._fragments.push(buf);
5468
5458
  }
5469
5459
  this.dataMessage(cb);
@@ -5486,6 +5476,7 @@ var require_receiver = __commonJS({
5486
5476
  this._totalPayloadLength = 0;
5487
5477
  this._messageLength = 0;
5488
5478
  this._fragmented = 0;
5479
+ this._numFragments = 0;
5489
5480
  this._fragments = [];
5490
5481
  if (this._opcode === 2) {
5491
5482
  let data;
@@ -6986,8 +6977,8 @@ var require_websocket = __commonJS({
6986
6977
  autoPong: true,
6987
6978
  closeTimeout: CLOSE_TIMEOUT,
6988
6979
  protocolVersion: protocolVersions[1],
6989
- maxBufferedChunks: 1024 * 1024,
6990
- maxFragments: 128 * 1024,
6980
+ maxBufferedChunks: 256 * 1024,
6981
+ maxFragments: 16 * 1024,
6991
6982
  maxPayload: 100 * 1024 * 1024,
6992
6983
  skipUTF8Validation: false,
6993
6984
  perMessageDeflate: true,
@@ -7574,9 +7565,9 @@ var require_websocket_server = __commonJS({
7574
7565
  * called
7575
7566
  * @param {Function} [options.handleProtocols] A hook to handle protocols
7576
7567
  * @param {String} [options.host] The hostname where to bind the server
7577
- * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
7568
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
7578
7569
  * buffered data chunks
7579
- * @param {Number} [options.maxFragments=131072] The maximum number of message
7570
+ * @param {Number} [options.maxFragments=16384] The maximum number of message
7580
7571
  * fragments
7581
7572
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
7582
7573
  * size
@@ -7599,8 +7590,8 @@ var require_websocket_server = __commonJS({
7599
7590
  options = {
7600
7591
  allowSynchronousEvents: true,
7601
7592
  autoPong: true,
7602
- maxBufferedChunks: 1024 * 1024,
7603
- maxFragments: 128 * 1024,
7593
+ maxBufferedChunks: 256 * 1024,
7594
+ maxFragments: 16 * 1024,
7604
7595
  maxPayload: 100 * 1024 * 1024,
7605
7596
  skipUTF8Validation: false,
7606
7597
  perMessageDeflate: false,
@@ -13526,6 +13517,48 @@ function toSummary(session) {
13526
13517
  provider: session.provider
13527
13518
  };
13528
13519
  }
13520
+ var ActivityTracker = class {
13521
+ constructor(emitter) {
13522
+ this.emitter = emitter;
13523
+ this.live = /* @__PURE__ */ new Map();
13524
+ }
13525
+ /** Start or update one activity. Re-calling with the same id replaces the
13526
+ * row, which is how a step label advances (`setup` → "installing node"). */
13527
+ set(kind, state, label, detail = "", key = "") {
13528
+ const id = key ? `${kind}:${key}` : kind;
13529
+ const activity = {
13530
+ id,
13531
+ kind,
13532
+ state,
13533
+ label,
13534
+ detail,
13535
+ // Keep the original start so a step change doesn't reset the clock.
13536
+ startedAt: this.live.get(id)?.startedAt ?? Date.now()
13537
+ };
13538
+ this.live.set(id, activity);
13539
+ this.emitter.emitActivityUpdate(activity);
13540
+ }
13541
+ /** Retire an activity. Safe to call for one that never started. */
13542
+ clear(kind, key = "") {
13543
+ const id = key ? `${kind}:${key}` : kind;
13544
+ const existing = this.live.get(id);
13545
+ if (!existing) return;
13546
+ this.live.delete(id);
13547
+ this.emitter.emitActivityUpdate({ ...existing, state: null });
13548
+ }
13549
+ /** Everything live right now, for the replay a fresh channel needs. */
13550
+ list() {
13551
+ return [...this.live.values()];
13552
+ }
13553
+ /** Runner shutdown: nothing survives the process, so drop the rows. */
13554
+ destroy() {
13555
+ for (const id of [...this.live.keys()]) {
13556
+ const existing = this.live.get(id);
13557
+ this.live.delete(id);
13558
+ this.emitter.emitActivityUpdate({ ...existing, state: null });
13559
+ }
13560
+ }
13561
+ };
13529
13562
  function createEventSink(send, spaceSlug, notify) {
13530
13563
  const PHASE_LABELS = {
13531
13564
  research: "Research started",
@@ -13623,6 +13656,9 @@ function createEventSink(send, spaceSlug, notify) {
13623
13656
  },
13624
13657
  emitBareFsChange(postId, change) {
13625
13658
  send(channel(`bare-fs:${postId}`), change);
13659
+ },
13660
+ emitActivityUpdate(activity) {
13661
+ send(channel("activity-update"), activity);
13626
13662
  }
13627
13663
  };
13628
13664
  }
@@ -16887,6 +16923,7 @@ var SpaceCore = class {
16887
16923
  this.serverUrl = options.serverUrl;
16888
16924
  this.mcp = options.mcp;
16889
16925
  this.requestSudo = options.requestSudo;
16926
+ this.activity = new ActivityTracker(this.emitter);
16890
16927
  }
16891
16928
  setLoggedIn(provider, value2) {
16892
16929
  this.loggedIn[provider] = value2;
@@ -17036,10 +17073,26 @@ var SpaceCore = class {
17036
17073
  const status = (msg) => {
17037
17074
  onStatus?.(msg);
17038
17075
  this.emitter.emitSetupStatus(msg);
17076
+ if (msg && !msg.startsWith(SETUP_LOG_PREFIX)) {
17077
+ this.activity.set("setup", "working", "Setting up space", msg);
17078
+ }
17039
17079
  };
17040
17080
  if (this.setupPromise) return this.setupPromise;
17041
17081
  if (this.ready) {
17042
- if (refreshGit) await this.provisionGitLane(status);
17082
+ if (refreshGit) {
17083
+ try {
17084
+ await this.provisionGitLane(status);
17085
+ this.activity.clear("setup");
17086
+ } catch (err) {
17087
+ this.activity.set(
17088
+ "setup",
17089
+ "error",
17090
+ "Setup failed",
17091
+ err instanceof Error ? err.message : String(err)
17092
+ );
17093
+ throw err;
17094
+ }
17095
+ }
17043
17096
  return;
17044
17097
  }
17045
17098
  this.setupPromise = (async () => {
@@ -17074,8 +17127,17 @@ var SpaceCore = class {
17074
17127
  ]);
17075
17128
  status("");
17076
17129
  this.ready = true;
17130
+ } catch (err) {
17131
+ this.activity.set(
17132
+ "setup",
17133
+ "error",
17134
+ "Setup failed",
17135
+ err instanceof Error ? err.message : String(err)
17136
+ );
17137
+ throw err;
17077
17138
  } finally {
17078
17139
  this.setupPromise = null;
17140
+ if (this.ready) this.activity.clear("setup");
17079
17141
  }
17080
17142
  })();
17081
17143
  return this.setupPromise;
@@ -17170,6 +17232,7 @@ var SpaceCore = class {
17170
17232
  destroy() {
17171
17233
  this.ready = false;
17172
17234
  this.setupPromise = null;
17235
+ this.activity.destroy();
17173
17236
  }
17174
17237
  };
17175
17238
 
@@ -17183,6 +17246,7 @@ var mergedCardSchema = external_exports.object({
17183
17246
  title: external_exports.string().trim().min(1).max(255),
17184
17247
  content: external_exports.string().max(5e3)
17185
17248
  });
17249
+ var errText = (err) => err instanceof Error ? err.message : String(err);
17186
17250
  var BoardAgentEngine = class _BoardAgentEngine {
17187
17251
  constructor(options) {
17188
17252
  // In-flight creator session, memory-only (the repo is the source of truth
@@ -17235,6 +17299,12 @@ var BoardAgentEngine = class _BoardAgentEngine {
17235
17299
  this.claude = new ClaudeModeEngine(this.core, this.emitter);
17236
17300
  this.terminal = new TerminalModeEngine(this.core, this.emitter);
17237
17301
  }
17302
+ /** Everything the runner does for this space that isn't a card session, so
17303
+ * the Running jobs panel can report the whole machine. Owned by the core,
17304
+ * which the setup path also drives. */
17305
+ get activity() {
17306
+ return this.core.activity;
17307
+ }
17238
17308
  /** How this host protects stored credentials. Read-only: the daemon answers
17239
17309
  * unlock and password changes itself (SECURE_ACTIONS in daemon.ts). */
17240
17310
  secureStatus() {
@@ -17315,6 +17385,11 @@ var BoardAgentEngine = class _BoardAgentEngine {
17315
17385
  throw new Error("An environment fix is already running.");
17316
17386
  }
17317
17387
  this.envState = { phase, logs: [], result: null, error: "" };
17388
+ this.activity.set(
17389
+ "deploy-env",
17390
+ "working",
17391
+ phase === "fixing" ? "Fixing deploy environment" : "Checking deploy environment"
17392
+ );
17318
17393
  try {
17319
17394
  const result = await work();
17320
17395
  this.envState = {
@@ -17324,15 +17399,22 @@ var BoardAgentEngine = class _BoardAgentEngine {
17324
17399
  error: ""
17325
17400
  };
17326
17401
  this.emitter.emitDeployEnv({ phase: "done" });
17402
+ this.activity.clear("deploy-env");
17327
17403
  return result;
17328
17404
  } catch (err) {
17329
17405
  this.envState = {
17330
17406
  phase: "done",
17331
17407
  logs: this.envState.logs,
17332
17408
  result: null,
17333
- error: err instanceof Error ? err.message : String(err)
17409
+ error: errText(err)
17334
17410
  };
17335
17411
  this.emitter.emitDeployEnv({ phase: "error" });
17412
+ this.activity.set(
17413
+ "deploy-env",
17414
+ "error",
17415
+ "Deploy environment failed",
17416
+ errText(err)
17417
+ );
17336
17418
  throw err;
17337
17419
  }
17338
17420
  }
@@ -17370,6 +17452,36 @@ var BoardAgentEngine = class _BoardAgentEngine {
17370
17452
  setRun(partial) {
17371
17453
  this.runState = { ...this.runState, ...partial };
17372
17454
  this.emitter.emitDeployRun({ phase: this.runState.phase });
17455
+ this.syncRunActivity();
17456
+ }
17457
+ /** Mirror the deploy run's phase onto the Running jobs panel. A deploy can
17458
+ * sit on `awaiting-input` for hours, which is exactly the state worth
17459
+ * seeing without opening the modal. */
17460
+ syncRunActivity() {
17461
+ const { phase, question, pendingCommand, error } = this.runState;
17462
+ switch (phase) {
17463
+ case "running":
17464
+ return this.activity.set("deploy-run", "working", "Deploying");
17465
+ case "awaiting-input":
17466
+ return this.activity.set(
17467
+ "deploy-run",
17468
+ "awaiting-input",
17469
+ "Deploy needs your answer",
17470
+ question
17471
+ );
17472
+ case "awaiting-auth":
17473
+ return this.activity.set(
17474
+ "deploy-run",
17475
+ "awaiting-input",
17476
+ "Deploy needs authorization",
17477
+ pendingCommand
17478
+ );
17479
+ case "failed":
17480
+ return this.activity.set("deploy-run", "error", "Deploy failed", error);
17481
+ // success / idle: the run is over and the modal holds the report.
17482
+ default:
17483
+ return this.activity.clear("deploy-run");
17484
+ }
17373
17485
  }
17374
17486
  deployRunStart(sendLog) {
17375
17487
  if (this.runState.phase === "running" || this.runState.phase === "awaiting-input" || this.runState.phase === "awaiting-auth") {
@@ -17389,6 +17501,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
17389
17501
  finishedAt: 0
17390
17502
  };
17391
17503
  this.emitter.emitDeployRun({ phase: "running" });
17504
+ this.syncRunActivity();
17392
17505
  const bufferLog = (entry) => {
17393
17506
  const logs = this.runState.logs;
17394
17507
  const last = logs[logs.length - 1];
@@ -17567,6 +17680,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
17567
17680
  }
17568
17681
  async runCreatorPass(work) {
17569
17682
  this.creatorState = { phase: "working", logs: [], draft: null, error: "" };
17683
+ this.activity.set("deploy-skill", "working", "Writing deploy skill");
17570
17684
  try {
17571
17685
  const draft = await work();
17572
17686
  this.creatorState = {
@@ -17576,15 +17690,21 @@ var BoardAgentEngine = class _BoardAgentEngine {
17576
17690
  error: ""
17577
17691
  };
17578
17692
  this.emitter.emitDeployCreator({ phase: "review" });
17693
+ this.activity.set(
17694
+ "deploy-skill",
17695
+ "awaiting-input",
17696
+ "Deploy skill ready to review"
17697
+ );
17579
17698
  return draft;
17580
17699
  } catch (err) {
17581
- const error = err instanceof Error ? err.message : String(err);
17700
+ const error = errText(err);
17582
17701
  this.creatorState = {
17583
17702
  ...this.creatorState,
17584
17703
  phase: this.creatorState.draft ? "review" : "idle",
17585
17704
  error
17586
17705
  };
17587
17706
  this.emitter.emitDeployCreator({ phase: "error", error });
17707
+ this.activity.set("deploy-skill", "error", "Deploy skill failed", error);
17588
17708
  throw err;
17589
17709
  }
17590
17710
  }
@@ -17663,6 +17783,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
17663
17783
  this.skillCreatorSessionId = null;
17664
17784
  this.skillDraftBranch = null;
17665
17785
  this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
17786
+ this.activity.clear("deploy-skill");
17666
17787
  return approveDeploySkill({
17667
17788
  spaceDir: this.core.spaceDir(),
17668
17789
  gitName: config.gitName,
@@ -17674,6 +17795,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
17674
17795
  this.skillCreatorSessionId = null;
17675
17796
  this.skillDraftBranch = null;
17676
17797
  this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
17798
+ this.activity.clear("deploy-skill");
17677
17799
  return discardDeploySkill({ spaceDir: this.core.spaceDir() });
17678
17800
  }
17679
17801
  // Start the host-run device-code flow and return the code to show the user.
@@ -17683,11 +17805,15 @@ var BoardAgentEngine = class _BoardAgentEngine {
17683
17805
  console.log(
17684
17806
  `[onedrive] device-code started: userCode=${start.userCode} expiresIn=${start.expiresIn}s interval=${start.interval}s`
17685
17807
  );
17686
- void this.pollDeviceCodeUntilDone(
17687
- p.clientId,
17688
- start.deviceCode,
17689
- start.interval,
17690
- start.expiresIn
17808
+ this.trackSignin(
17809
+ "onedrive",
17810
+ "OneDrive sign-in",
17811
+ this.pollDeviceCodeUntilDone(
17812
+ p.clientId,
17813
+ start.deviceCode,
17814
+ start.interval,
17815
+ start.expiresIn
17816
+ )
17691
17817
  );
17692
17818
  return {
17693
17819
  userCode: start.userCode,
@@ -17755,11 +17881,15 @@ var BoardAgentEngine = class _BoardAgentEngine {
17755
17881
  console.log(
17756
17882
  `[github] device-code started: userCode=${start.userCode} expiresIn=${start.expiresIn}s interval=${start.interval}s`
17757
17883
  );
17758
- void this.pollGithubUntilDone(
17759
- p.clientId,
17760
- start.deviceCode,
17761
- start.interval,
17762
- start.expiresIn
17884
+ this.trackSignin(
17885
+ "github",
17886
+ "GitHub sign-in",
17887
+ this.pollGithubUntilDone(
17888
+ p.clientId,
17889
+ start.deviceCode,
17890
+ start.interval,
17891
+ start.expiresIn
17892
+ )
17763
17893
  );
17764
17894
  return {
17765
17895
  userCode: start.userCode,
@@ -17844,6 +17974,24 @@ var BoardAgentEngine = class _BoardAgentEngine {
17844
17974
  if (!githubToken) throw new Error("Connect GitHub first.");
17845
17975
  return listGithubBranches(githubToken, p.fullName);
17846
17976
  }
17977
+ /** A device-code sign-in is blocked on the user until the code expires, so
17978
+ * it reports as awaiting-input rather than working. Keyed so GitHub and
17979
+ * Claude can both be pending without overwriting each other. */
17980
+ beginSignin(key, label) {
17981
+ this.activity.set(
17982
+ "signin",
17983
+ "awaiting-input",
17984
+ label,
17985
+ "Waiting for you to finish signing in",
17986
+ key
17987
+ );
17988
+ }
17989
+ /** Same, for the two flows whose completion is a settled promise rather
17990
+ * than a callback. Neither poller rejects; both end on their own deadline. */
17991
+ trackSignin(key, label, poll) {
17992
+ this.beginSignin(key, label);
17993
+ void poll.finally(() => this.activity.clear("signin", key));
17994
+ }
17847
17995
  async claudeLoginStart() {
17848
17996
  if (!this.core.isReady()) {
17849
17997
  throw new Error("Initialize the environment first, then sign in.");
@@ -17855,11 +18003,14 @@ var BoardAgentEngine = class _BoardAgentEngine {
17855
18003
  if (result.status === "complete") {
17856
18004
  this.core.setLoggedIn("claude", true);
17857
18005
  }
18006
+ this.activity.clear("signin", "claude");
17858
18007
  this.emitter.emitClaudeAuth(result);
17859
18008
  }
17860
18009
  );
17861
18010
  if (this.claudeLogin.active) await this.claudeLogin.cancel();
17862
- return this.claudeLogin.start();
18011
+ const started = await this.claudeLogin.start();
18012
+ this.beginSignin("claude", "Claude sign-in");
18013
+ return started;
17863
18014
  }
17864
18015
  claudeLoginCode(p) {
17865
18016
  if (!this.claudeLogin) throw new Error("No Claude sign-in in progress.");
@@ -17867,6 +18018,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
17867
18018
  }
17868
18019
  async claudeLoginCancel() {
17869
18020
  await this.claudeLogin?.cancel();
18021
+ this.activity.clear("signin", "claude");
17870
18022
  return { success: true };
17871
18023
  }
17872
18024
  async codexLoginStart() {
@@ -17880,14 +18032,18 @@ var BoardAgentEngine = class _BoardAgentEngine {
17880
18032
  if (result.status === "complete") {
17881
18033
  this.core.setLoggedIn("codex", true);
17882
18034
  }
18035
+ this.activity.clear("signin", "codex");
17883
18036
  this.emitter.emitCodexAuth(result);
17884
18037
  }
17885
18038
  );
17886
18039
  if (this.codexLogin.active) await this.codexLogin.cancel();
17887
- return this.codexLogin.start();
18040
+ const started = await this.codexLogin.start();
18041
+ this.beginSignin("codex", "Codex sign-in");
18042
+ return started;
17888
18043
  }
17889
18044
  async codexLoginCancel() {
17890
18045
  await this.codexLogin?.cancel();
18046
+ this.activity.clear("signin", "codex");
17891
18047
  return { success: true };
17892
18048
  }
17893
18049
  async claudeLogout() {
@@ -17905,8 +18061,16 @@ var BoardAgentEngine = class _BoardAgentEngine {
17905
18061
  return { success: true };
17906
18062
  }
17907
18063
  // ── Ask AI + claude-only controls (→ claude) ──
17908
- ask(params, sendLog) {
17909
- return this.claude.ask(params, sendLog);
18064
+ async ask(params, sendLog) {
18065
+ this.activity.set("ask", "working", "Ask AI", params.question.slice(0, 80));
18066
+ try {
18067
+ const answer = await this.claude.ask(params, sendLog);
18068
+ this.activity.clear("ask");
18069
+ return answer;
18070
+ } catch (err) {
18071
+ this.activity.set("ask", "error", "Ask AI", errText(err));
18072
+ throw err;
18073
+ }
17910
18074
  }
17911
18075
  resetBoardSession() {
17912
18076
  this.claude.resetBoardSession();
@@ -23663,6 +23827,25 @@ var localConfigProvider = {
23663
23827
  };
23664
23828
 
23665
23829
  // src/daemon.ts
23830
+ function toRtcIceServers(servers) {
23831
+ const fallback = ["stun:stun.l.google.com:19302"];
23832
+ if (!servers?.length) return fallback;
23833
+ const out = [];
23834
+ for (const server of servers) {
23835
+ const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
23836
+ for (const url2 of urls) {
23837
+ const turn = /^(turns?):(.+)$/.exec(url2);
23838
+ if (!turn || !server.username || !server.credential) {
23839
+ out.push(url2);
23840
+ continue;
23841
+ }
23842
+ const user = encodeURIComponent(server.username);
23843
+ const pass = encodeURIComponent(server.credential);
23844
+ out.push(`${turn[1]}:${user}:${pass}@${turn[2]}`);
23845
+ }
23846
+ }
23847
+ return out.length > 0 ? out : fallback;
23848
+ }
23666
23849
  function toChannelScope(raw) {
23667
23850
  if (!raw || raw.role === "owner") return { role: "owner" };
23668
23851
  return { role: "member", spaceSlugs: new Set(raw.spaceSlugs) };
@@ -23827,15 +24010,35 @@ async function startDaemon(config) {
23827
24010
  },
23828
24011
  webhook,
23829
24012
  full,
23830
- (p) => broadcastToBoard(
23831
- spaceSlug,
23832
- JSON.stringify({
23833
- type: "event",
24013
+ (p) => {
24014
+ broadcastToBoard(
23834
24015
  spaceSlug,
23835
- event: "onedrive-index",
23836
- payload: { status: p.status, done: p.done, error: p.error }
23837
- })
23838
- )
24016
+ JSON.stringify({
24017
+ type: "event",
24018
+ spaceSlug,
24019
+ event: "onedrive-index",
24020
+ payload: { status: p.status, done: p.done, error: p.error }
24021
+ })
24022
+ );
24023
+ const activity = getEngine(spaceSlug).activity;
24024
+ if (p.status === "indexing") {
24025
+ activity.set(
24026
+ "onedrive-index",
24027
+ "working",
24028
+ "Indexing OneDrive",
24029
+ `${p.done} files`
24030
+ );
24031
+ } else if (p.status === "error") {
24032
+ activity.set(
24033
+ "onedrive-index",
24034
+ "error",
24035
+ "OneDrive index failed",
24036
+ p.error ?? ""
24037
+ );
24038
+ } else {
24039
+ activity.clear("onedrive-index");
24040
+ }
24041
+ }
23839
24042
  ).catch((err) => {
23840
24043
  console.error(
23841
24044
  `[onedrive] index start failed for ${spaceSlug}:`,
@@ -23923,9 +24126,11 @@ async function startDaemon(config) {
23923
24126
  console.log(`Received WebRTC offer from ${data.fromSocketId}`);
23924
24127
  const scope = toChannelScope(data.scope);
23925
24128
  try {
23926
- const peer = new import_node_datachannel.PeerConnection("runner", {
23927
- iceServers: ["stun:stun.l.google.com:19302"]
23928
- });
24129
+ const iceServers = toRtcIceServers(data.iceServers);
24130
+ console.log(
24131
+ `[webrtc] ICE servers: ${iceServers.map((u) => u.replace(/:[^:@]+@/, ":***@")).join(", ")}`
24132
+ );
24133
+ const peer = new import_node_datachannel.PeerConnection("runner", { iceServers });
23929
24134
  const iceCandidateHandler = (msg) => {
23930
24135
  if (msg.fromSocketId === data.fromSocketId) {
23931
24136
  console.log(`[webrtc] remote ICE candidate: ${msg.candidate.candidate.slice(0, 60)}...`);
@@ -24009,6 +24214,16 @@ async function startDaemon(config) {
24009
24214
  });
24010
24215
  sendToChannel(msg);
24011
24216
  }
24217
+ for (const activity of engine.activity.list()) {
24218
+ sendToChannel(
24219
+ JSON.stringify({
24220
+ type: "event",
24221
+ spaceSlug,
24222
+ event: `board-agent:${spaceSlug}:activity-update`,
24223
+ payload: activity
24224
+ })
24225
+ );
24226
+ }
24012
24227
  }
24013
24228
  });
24014
24229
  dc.onMessage((raw) => {
package/index/index.js CHANGED
@@ -3112,7 +3112,6 @@ var require_utils = __commonJS({
3112
3112
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3113
3113
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3114
3114
  var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3115
- var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/?]$/iu);
3116
3115
  function stringArrayToHexStripped(input) {
3117
3116
  let acc = "";
3118
3117
  let code = 0;
@@ -3255,7 +3254,7 @@ var require_utils = __commonJS({
3255
3254
  continue;
3256
3255
  }
3257
3256
  } else if (input[0] === "/") {
3258
- if (input[1] === ".") {
3257
+ if (input[1] === "." || input[1] === "/") {
3259
3258
  output.push("/");
3260
3259
  break;
3261
3260
  }
@@ -3337,30 +3336,10 @@ var require_utils = __commonJS({
3337
3336
  }
3338
3337
  return output;
3339
3338
  }
3340
- var BYTE_HEX = new Array(256);
3341
- {
3342
- const HEX_DIGITS = "0123456789ABCDEF";
3343
- for (let i = 0; i < 256; i++) {
3344
- BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3345
- }
3346
- }
3347
- function isEscapeSafe(cp2) {
3348
- return cp2 >= 48 && cp2 <= 57 || cp2 >= 65 && cp2 <= 90 || cp2 >= 97 && cp2 <= 122 || cp2 === 42 || cp2 === 43 || cp2 === 45 || cp2 === 46 || cp2 === 47 || cp2 === 64 || cp2 === 95;
3349
- }
3350
- function percentEncodeNonAscii(cp2) {
3351
- if (cp2 < 2048) {
3352
- return BYTE_HEX[192 | cp2 >> 6] + BYTE_HEX[128 | cp2 & 63];
3353
- }
3354
- if (cp2 < 65536) {
3355
- return BYTE_HEX[224 | cp2 >> 12] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
3356
- }
3357
- return BYTE_HEX[240 | cp2 >> 18] + BYTE_HEX[128 | cp2 >> 12 & 63] + BYTE_HEX[128 | cp2 >> 6 & 63] + BYTE_HEX[128 | cp2 & 63];
3358
- }
3359
3339
  function normalizePathEncoding(input) {
3360
3340
  let output = "";
3361
3341
  for (let i = 0; i < input.length; i++) {
3362
- const ch2 = input[i];
3363
- if (ch2 === "%" && i + 2 < input.length) {
3342
+ if (input[i] === "%" && i + 2 < input.length) {
3364
3343
  const hex = input.slice(i + 1, i + 3);
3365
3344
  if (isHexPair(hex)) {
3366
3345
  const normalizedHex = hex.toUpperCase();
@@ -3374,66 +3353,10 @@ var require_utils = __commonJS({
3374
3353
  continue;
3375
3354
  }
3376
3355
  }
3377
- if (isPathCharacter(ch2)) {
3378
- output += ch2;
3379
- } else {
3380
- const code = input.charCodeAt(i);
3381
- if (code < 128) {
3382
- output += isEscapeSafe(code) ? ch2 : BYTE_HEX[code];
3383
- } else if (code < 55296 || code > 57343) {
3384
- output += percentEncodeNonAscii(code);
3385
- } else if (code <= 56319 && i + 1 < input.length) {
3386
- const low = input.charCodeAt(i + 1);
3387
- if (low >= 56320 && low <= 57343) {
3388
- output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3389
- i++;
3390
- } else {
3391
- output += percentEncodeNonAscii(65533);
3392
- }
3393
- } else {
3394
- output += percentEncodeNonAscii(65533);
3395
- }
3396
- }
3397
- }
3398
- return output;
3399
- }
3400
- function normalizeQueryFragmentEncoding(input) {
3401
- let output = "";
3402
- for (let i = 0; i < input.length; i++) {
3403
- const ch2 = input[i];
3404
- if (ch2 === "%" && i + 2 < input.length) {
3405
- const hex = input.slice(i + 1, i + 3);
3406
- if (isHexPair(hex)) {
3407
- const normalizedHex = hex.toUpperCase();
3408
- const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3409
- if (isUnreserved(decoded)) {
3410
- output += decoded;
3411
- } else {
3412
- output += "%" + normalizedHex;
3413
- }
3414
- i += 2;
3415
- continue;
3416
- }
3417
- }
3418
- if (isQueryFragmentCharacter(ch2)) {
3419
- output += ch2;
3356
+ if (isPathCharacter(input[i])) {
3357
+ output += input[i];
3420
3358
  } else {
3421
- const code = input.charCodeAt(i);
3422
- if (code < 128) {
3423
- output += isEscapeSafe(code) ? ch2 : BYTE_HEX[code];
3424
- } else if (code < 55296 || code > 57343) {
3425
- output += percentEncodeNonAscii(code);
3426
- } else if (code <= 56319 && i + 1 < input.length) {
3427
- const low = input.charCodeAt(i + 1);
3428
- if (low >= 56320 && low <= 57343) {
3429
- output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3430
- i++;
3431
- } else {
3432
- output += percentEncodeNonAscii(65533);
3433
- }
3434
- } else {
3435
- output += percentEncodeNonAscii(65533);
3436
- }
3359
+ output += escape(input[i]);
3437
3360
  }
3438
3361
  }
3439
3362
  return output;
@@ -3441,8 +3364,7 @@ var require_utils = __commonJS({
3441
3364
  function escapePreservingEscapes(input) {
3442
3365
  let output = "";
3443
3366
  for (let i = 0; i < input.length; i++) {
3444
- const ch2 = input[i];
3445
- if (ch2 === "%" && i + 2 < input.length) {
3367
+ if (input[i] === "%" && i + 2 < input.length) {
3446
3368
  const hex = input.slice(i + 1, i + 3);
3447
3369
  if (isHexPair(hex)) {
3448
3370
  output += "%" + hex.toUpperCase();
@@ -3450,22 +3372,7 @@ var require_utils = __commonJS({
3450
3372
  continue;
3451
3373
  }
3452
3374
  }
3453
- const code = input.charCodeAt(i);
3454
- if (code < 128) {
3455
- output += isEscapeSafe(code) ? ch2 : BYTE_HEX[code];
3456
- } else if (code < 55296 || code > 57343) {
3457
- output += percentEncodeNonAscii(code);
3458
- } else if (code <= 56319 && i + 1 < input.length) {
3459
- const low = input.charCodeAt(i + 1);
3460
- if (low >= 56320 && low <= 57343) {
3461
- output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3462
- i++;
3463
- } else {
3464
- output += percentEncodeNonAscii(65533);
3465
- }
3466
- } else {
3467
- output += percentEncodeNonAscii(65533);
3468
- }
3375
+ output += escape(input[i]);
3469
3376
  }
3470
3377
  return output;
3471
3378
  }
@@ -3499,7 +3406,6 @@ var require_utils = __commonJS({
3499
3406
  reescapeHostDelimiters,
3500
3407
  normalizePercentEncoding,
3501
3408
  normalizePathEncoding,
3502
- normalizeQueryFragmentEncoding,
3503
3409
  escapePreservingEscapes,
3504
3410
  removeDotSegments,
3505
3411
  isIPv4,
@@ -3724,7 +3630,7 @@ var require_schemes = __commonJS({
3724
3630
  var require_fast_uri = __commonJS({
3725
3631
  "../../node_modules/fast-uri/index.js"(exports2, module2) {
3726
3632
  "use strict";
3727
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, normalizeQueryFragmentEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3633
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3728
3634
  var { SCHEMES, getSchemeHandler } = require_schemes();
3729
3635
  function normalize(uri, options) {
3730
3636
  if (typeof uri === "string") {
@@ -3738,7 +3644,12 @@ var require_fast_uri = __commonJS({
3738
3644
  }
3739
3645
  function resolve(baseURI, relativeURI, options) {
3740
3646
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3741
- const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3647
+ const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3648
+ const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3649
+ if (baseMalformed || relativeMalformed) {
3650
+ throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3651
+ }
3652
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3742
3653
  schemelessOptions.skipEscape = true;
3743
3654
  return serialize(resolved, schemelessOptions);
3744
3655
  }
@@ -3864,6 +3775,7 @@ var require_fast_uri = __commonJS({
3864
3775
  }
3865
3776
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3866
3777
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
3778
+ var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
3867
3779
  function getParseError(parsed, matches) {
3868
3780
  if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3869
3781
  return 'URI path must start with "/" when authority is present.';
@@ -3898,9 +3810,23 @@ var require_fast_uri = __commonJS({
3898
3810
  parsed.error = "URI authority must not contain a literal backslash.";
3899
3811
  malformedAuthorityOrPort = true;
3900
3812
  }
3813
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
3814
+ if (introducerMatch !== null) {
3815
+ const region = introducerMatch[1];
3816
+ const normalizedRegion = region.replace(/[\t\n\r]/g, "");
3817
+ if (normalizedRegion.length >= 2) {
3818
+ if (normalizedRegion.slice(0, 2) !== "//") {
3819
+ parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
3820
+ malformedAuthorityOrPort = true;
3821
+ } else if (region.length !== normalizedRegion.length) {
3822
+ parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
3823
+ malformedAuthorityOrPort = true;
3824
+ }
3825
+ }
3826
+ }
3901
3827
  const matches = uri.match(URI_PARSE);
3902
3828
  if (matches) {
3903
- parsed.scheme = matches[1] === void 0 ? void 0 : matches[1].toLowerCase();
3829
+ parsed.scheme = matches[1];
3904
3830
  parsed.userinfo = matches[3];
3905
3831
  parsed.host = matches[4];
3906
3832
  parsed.port = parseInt(matches[5], 10);
@@ -3959,11 +3885,12 @@ var require_fast_uri = __commonJS({
3959
3885
  if (parsed.path) {
3960
3886
  parsed.path = normalizePathEncoding(parsed.path);
3961
3887
  }
3962
- if (parsed.query) {
3963
- parsed.query = normalizeQueryFragmentEncoding(parsed.query);
3964
- }
3965
3888
  if (parsed.fragment) {
3966
- parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3889
+ try {
3890
+ parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3891
+ } catch {
3892
+ parsed.error = parsed.error || "URI malformed";
3893
+ }
3967
3894
  }
3968
3895
  }
3969
3896
  if (schemeHandler && schemeHandler.parse) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factiii/runner",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Factiii Runner, run Board AI agents on a machine you control. Pairs with the Factiii web/mobile clients over WebRTC.",
5
5
  "license": "ISC",
6
6
  "keywords": [
@@ -52,7 +52,7 @@
52
52
  "@types/node": "^20.0.0",
53
53
  "commander": "^12.1.0",
54
54
  "esbuild": "*",
55
- "socket.io-client": "^4.8.1",
55
+ "socket.io-client": "^4.8.3",
56
56
  "tsx": "^4.22.4",
57
57
  "typescript": "catalog:"
58
58
  }