@skaile/workspaces 3.7.3 → 3.8.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/app-sdk/index.js +1 -1
  3. package/dist/{asset-feeds-6XDLVHQJ.js → asset-feeds-7HM3HONM.js} +4 -4
  4. package/dist/{asset-feeds-6XDLVHQJ.js.map → asset-feeds-7HM3HONM.js.map} +1 -1
  5. package/dist/asset-manager/index.js +1 -1
  6. package/dist/bridge/drivers/claude-sdk.js +229 -23
  7. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  8. package/dist/bridge/drivers/codex.js +1 -1
  9. package/dist/bridge/drivers/continue.js +2 -2
  10. package/dist/bridge/drivers/echo.js +1 -1
  11. package/dist/bridge/drivers/gemini.js +3 -3
  12. package/dist/bridge/drivers/goose.js +2 -2
  13. package/dist/bridge/drivers/omp.js +1 -1
  14. package/dist/bridge/drivers/opencode.js +2 -2
  15. package/dist/bridge/drivers/qwen.js +3 -3
  16. package/dist/bridge/index.js +2 -2
  17. package/dist/bridge/src/drivers/claude-sdk.d.ts +96 -1
  18. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  19. package/dist/bridge/src/types.d.ts +103 -0
  20. package/dist/bridge/src/types.d.ts.map +1 -1
  21. package/dist/{chunk-WHOM6NF3.js → chunk-2GREL3OD.js} +3 -3
  22. package/dist/{chunk-WHOM6NF3.js.map → chunk-2GREL3OD.js.map} +1 -1
  23. package/dist/{chunk-ZMCBWZ77.js → chunk-2JW5SWIU.js} +6 -6
  24. package/dist/{chunk-ZMCBWZ77.js.map → chunk-2JW5SWIU.js.map} +1 -1
  25. package/dist/{chunk-5Z4JLURL.js → chunk-4WTC6BSV.js} +3 -3
  26. package/dist/{chunk-5Z4JLURL.js.map → chunk-4WTC6BSV.js.map} +1 -1
  27. package/dist/{chunk-DGWJAPRC.js → chunk-6GDVGAIZ.js} +447 -95
  28. package/dist/chunk-6GDVGAIZ.js.map +1 -0
  29. package/dist/{chunk-BSQGQMGA.js → chunk-7SHXLJQP.js} +3 -3
  30. package/dist/{chunk-BSQGQMGA.js.map → chunk-7SHXLJQP.js.map} +1 -1
  31. package/dist/{chunk-YICH6VQB.js → chunk-B7TA2XI7.js} +2 -2
  32. package/dist/{chunk-YICH6VQB.js.map → chunk-B7TA2XI7.js.map} +1 -1
  33. package/dist/{chunk-ML6OHA3S.js → chunk-BGAAZMBK.js} +2 -2
  34. package/dist/chunk-BGAAZMBK.js.map +1 -0
  35. package/dist/{chunk-765ZGKVC.js → chunk-DGDELPOD.js} +3 -3
  36. package/dist/{chunk-765ZGKVC.js.map → chunk-DGDELPOD.js.map} +1 -1
  37. package/dist/{chunk-26L2B6P6.js → chunk-FUCYTGAE.js} +4 -4
  38. package/dist/chunk-FUCYTGAE.js.map +1 -0
  39. package/dist/{chunk-6GTPLCMO.js → chunk-IH2KJYAU.js} +142 -13
  40. package/dist/chunk-IH2KJYAU.js.map +1 -0
  41. package/dist/{chunk-UGBN7MQZ.js → chunk-KNPBMR2P.js} +3 -3
  42. package/dist/{chunk-UGBN7MQZ.js.map → chunk-KNPBMR2P.js.map} +1 -1
  43. package/dist/{chunk-BCXI27BH.js → chunk-NVYEK4FI.js} +3 -3
  44. package/dist/{chunk-BCXI27BH.js.map → chunk-NVYEK4FI.js.map} +1 -1
  45. package/dist/{chunk-D4VHBAJC.js → chunk-O3LEV6KB.js} +4 -4
  46. package/dist/{chunk-D4VHBAJC.js.map → chunk-O3LEV6KB.js.map} +1 -1
  47. package/dist/{chunk-OOUQWDD5.js → chunk-OZZCPTOG.js} +6 -6
  48. package/dist/{chunk-OOUQWDD5.js.map → chunk-OZZCPTOG.js.map} +1 -1
  49. package/dist/{chunk-V7CKJGT4.js → chunk-PRUCNSBI.js} +4 -4
  50. package/dist/{chunk-V7CKJGT4.js.map → chunk-PRUCNSBI.js.map} +1 -1
  51. package/dist/{chunk-MGPHEYW2.js → chunk-QVQH6GV2.js} +2 -2
  52. package/dist/{chunk-MGPHEYW2.js.map → chunk-QVQH6GV2.js.map} +1 -1
  53. package/dist/{chunk-FSWYUSCP.js → chunk-RVXBOKHK.js} +3 -3
  54. package/dist/{chunk-FSWYUSCP.js.map → chunk-RVXBOKHK.js.map} +1 -1
  55. package/dist/cli/index.js +30 -30
  56. package/dist/connectors/index.js +3 -3
  57. package/dist/connectors/src/connector-manager.d.ts +34 -2
  58. package/dist/connectors/src/connector-manager.d.ts.map +1 -1
  59. package/dist/connectors/src/connector-prompt.d.ts.map +1 -1
  60. package/dist/connectors/src/connector-types.d.ts +25 -0
  61. package/dist/connectors/src/connector-types.d.ts.map +1 -1
  62. package/dist/connectors/src/credential-helper-script.d.ts +48 -0
  63. package/dist/connectors/src/credential-helper-script.d.ts.map +1 -1
  64. package/dist/connectors/src/index.d.ts +3 -3
  65. package/dist/connectors/src/index.d.ts.map +1 -1
  66. package/dist/connectors/src/managed-gitconfig.d.ts +80 -0
  67. package/dist/connectors/src/managed-gitconfig.d.ts.map +1 -1
  68. package/dist/{connectors-DY7UBBV5.js → connectors-2OHRZDJZ.js} +3 -3
  69. package/dist/{connectors-DY7UBBV5.js.map → connectors-2OHRZDJZ.js.map} +1 -1
  70. package/dist/{ensure-sources-TSHLAVNU.js → ensure-sources-42LTJ5QI.js} +4 -4
  71. package/dist/{ensure-sources-TSHLAVNU.js.map → ensure-sources-42LTJ5QI.js.map} +1 -1
  72. package/dist/factory-assets/connectors/box.js +3 -3
  73. package/dist/factory-assets/connectors/deploy.js +3 -3
  74. package/dist/factory-assets/connectors/devserver.js +3 -3
  75. package/dist/factory-assets/connectors/flow/adapter.js +3 -3
  76. package/dist/factory-assets/connectors/flow/run-flow.js +4 -4
  77. package/dist/factory-assets/connectors/flow.js +3 -3
  78. package/dist/factory-assets/connectors/git/driver.d.ts +28 -0
  79. package/dist/factory-assets/connectors/git/driver.d.ts.map +1 -1
  80. package/dist/factory-assets/connectors/git.js +3 -3
  81. package/dist/factory-assets/connectors/gmail.js +3 -3
  82. package/dist/factory-assets/connectors/googledrive.js +3 -3
  83. package/dist/factory-assets/connectors/local.js +3 -3
  84. package/dist/factory-assets/connectors/mattermost.js +3 -3
  85. package/dist/factory-assets/connectors/memory.js +3 -3
  86. package/dist/factory-assets/connectors/minio.js +3 -3
  87. package/dist/factory-assets/connectors/postgres.js +3 -3
  88. package/dist/factory-assets/connectors/s3.js +3 -3
  89. package/dist/factory-assets/connectors/sharepoint.js +3 -3
  90. package/dist/factory-assets/connectors/sqlite.js +3 -3
  91. package/dist/factory-assets/connectors/static-server.js +3 -3
  92. package/dist/factory-assets/connectors/tunnel.js +3 -3
  93. package/dist/factory-assets/connectors/webdav.js +3 -3
  94. package/dist/factory-assets/connectors/xstate-store.js +3 -3
  95. package/dist/factory-assets/connectors/xstate.js +3 -3
  96. package/dist/{helpers-PVM4MQV2.js → helpers-BP7FX6CO.js} +4 -4
  97. package/dist/{helpers-PVM4MQV2.js.map → helpers-BP7FX6CO.js.map} +1 -1
  98. package/dist/{library-publish-P7ZVEY6I.js → library-publish-X5Z32WJY.js} +6 -6
  99. package/dist/{library-publish-P7ZVEY6I.js.map → library-publish-X5Z32WJY.js.map} +1 -1
  100. package/dist/{open-library-QG4LHFQQ.js → open-library-PHUMBYHP.js} +5 -5
  101. package/dist/{open-library-QG4LHFQQ.js.map → open-library-PHUMBYHP.js.map} +1 -1
  102. package/dist/{project-YE6GKQTZ.js → project-HAMCXPNE.js} +5 -5
  103. package/dist/{project-YE6GKQTZ.js.map → project-HAMCXPNE.js.map} +1 -1
  104. package/dist/runner/index.js +7 -7
  105. package/dist/runner/src/ai-provider-switch.d.ts +95 -0
  106. package/dist/runner/src/ai-provider-switch.d.ts.map +1 -0
  107. package/dist/runner/src/credential-rotated.d.ts +23 -4
  108. package/dist/runner/src/credential-rotated.d.ts.map +1 -1
  109. package/dist/runner/src/serve.d.ts.map +1 -1
  110. package/dist/runner/src/session-builder.d.ts +19 -0
  111. package/dist/runner/src/session-builder.d.ts.map +1 -1
  112. package/dist/sdk/asset-manager.js +1 -1
  113. package/dist/sdk/bridge.js +2 -2
  114. package/dist/sdk/index.js +7 -7
  115. package/dist/sdk/runner.js +7 -7
  116. package/dist/sdk/session.js +2 -2
  117. package/dist/sdk/types.js +1 -1
  118. package/dist/session/index.js +2 -2
  119. package/dist/{setup-UYLZFES6.js → setup-IOOGR3EL.js} +4 -4
  120. package/dist/{setup-UYLZFES6.js.map → setup-IOOGR3EL.js.map} +1 -1
  121. package/dist/{source-5GTHOFT6.js → source-772GLNM6.js} +6 -6
  122. package/dist/{source-5GTHOFT6.js.map → source-772GLNM6.js.map} +1 -1
  123. package/dist/{source-manifest-publish-DK3CPBSI.js → source-manifest-publish-LAUGDQ5G.js} +7 -7
  124. package/dist/{source-manifest-publish-DK3CPBSI.js.map → source-manifest-publish-LAUGDQ5G.js.map} +1 -1
  125. package/dist/tui/index.js +7 -7
  126. package/dist/types/index.js +1 -1
  127. package/dist/types/src/events.d.ts +34 -1
  128. package/dist/types/src/events.d.ts.map +1 -1
  129. package/dist/types/src/index.d.ts +1 -1
  130. package/dist/types/src/index.d.ts.map +1 -1
  131. package/dist/types/src/lifecycle.d.ts +13 -0
  132. package/dist/types/src/lifecycle.d.ts.map +1 -1
  133. package/dist/types/src/version.d.ts +22 -1
  134. package/dist/types/src/version.d.ts.map +1 -1
  135. package/dist/workspace-plugin/adapters/mcp.js +2 -2
  136. package/dist/workspace-plugin/adapters/mcp.js.map +1 -1
  137. package/dist/workspace-plugin/adapters/omp.js +8 -8
  138. package/dist/workspace-plugin/adapters/omp.js.map +1 -1
  139. package/dist/workspace-plugin/index.js +1 -1
  140. package/dist/workspace-plugin/src/adapters/mcp.d.ts.map +1 -1
  141. package/dist/workspace-plugin/src/adapters/omp.d.ts.map +1 -1
  142. package/dist/workspace-plugin/src/tools/connectors.d.ts +7 -3
  143. package/dist/workspace-plugin/src/tools/connectors.d.ts.map +1 -1
  144. package/package.json +1 -1
  145. package/dist/chunk-26L2B6P6.js.map +0 -1
  146. package/dist/chunk-6GTPLCMO.js.map +0 -1
  147. package/dist/chunk-DGWJAPRC.js.map +0 -1
  148. package/dist/chunk-ML6OHA3S.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { resolveAuthRef } from './chunk-3UU3D2Q3.js';
2
- import { EventNormalizer } from './chunk-UGBN7MQZ.js';
2
+ import { EventNormalizer } from './chunk-KNPBMR2P.js';
3
3
  import { resolveModelTier } from './chunk-V34ZEXSE.js';
4
- import { createDriver } from './chunk-ML6OHA3S.js';
4
+ import { createDriver } from './chunk-BGAAZMBK.js';
5
5
  import { pluginRegistry } from './chunk-6E6PKKAD.js';
6
6
  import { computeStimulus } from './chunk-IFGTRL2I.js';
7
7
  import { resolveNodeBindings, getConsumedOutputSchema, validateConsumedNodeOutput, appendAttempt, resolveTerminalFailureStatus, remainingActiveExecutionMs, interpolateNodeInstruction, deriveCheckEvidence, computeFlowStateFromSnapshots, availableRouterFrontier, evaluateRouterSelection, routerSelectionOutput, planRouterSkips, unsettledRouterBranchIds, countFailedAttempts, hasAutomaticRetryBudget, persistedRouterSelection } from './chunk-OYZIURPH.js';
@@ -17,7 +17,7 @@ import { mkdir, rename, writeFile, readFile, stat, cp, readdir, chmod, realpath
17
17
  import * as z4 from 'zod';
18
18
  import { execFile } from 'child_process';
19
19
  import { promisify } from 'util';
20
- import { createReadStream, readFileSync, existsSync, mkdirSync, openSync, writeSync, closeSync, chmodSync, renameSync } from 'fs';
20
+ import { createReadStream, existsSync, mkdirSync, openSync, closeSync, chmodSync, utimesSync, readFileSync, writeSync, renameSync } from 'fs';
21
21
  import { createServer } from 'http';
22
22
  import mime from 'mime';
23
23
  import { createRequire } from 'module';
@@ -1494,53 +1494,59 @@ var GitConnector = class extends AbstractConnector {
1494
1494
  if (!isValidGitRefOrSha(branch)) {
1495
1495
  throw new Error(`Git connector: invalid branch name "${branch}"`);
1496
1496
  }
1497
- this.log.info("mount start", { url, branch, targetDir });
1498
- const initial = await this.acquireInitialToken(declaration, secrets, tokenMediator);
1499
- const { session, sync, lifecycle } = resolveGitConfig({
1500
- mode: declaration.mode,
1501
- options: opts
1502
- });
1503
- const state = {
1504
- url,
1505
- branch,
1506
- activeBranch: branch,
1507
- token: initial.token,
1508
- session,
1509
- sync,
1510
- lifecycle,
1511
- targetDir,
1512
- tokenMediator: initial.authRef?.kind === "backend" ? tokenMediator : void 0
1513
- };
1514
- const authUrl = this.authUrl(state);
1515
- await this.prepareCheckout(targetDir, url, branch, authUrl, declaration.access, started);
1516
- if (session.enabled && !session.isMain && session.slug) {
1517
- await this.checkoutSessionBranch(state, session, targetDir);
1518
- }
1519
- if (sync.fetchIntervalSec > 0) {
1520
- this.startPeriodicFetch(state, targetDir);
1521
- }
1522
- if (declaration.mount?.exposeAccessToken === true) {
1523
- try {
1524
- this.exposeManagedCredential(declaration, state, url, initial);
1525
- } catch (err) {
1526
- this.log.error("managed-gitconfig write failed", err, { connectorId: declaration.id });
1527
- throw err;
1497
+ try {
1498
+ this.log.info("mount start", { url, branch, targetDir });
1499
+ this.clearUnavailableCredentialHelper(declaration.id, url);
1500
+ const initial = await this.acquireInitialToken(declaration, secrets, tokenMediator);
1501
+ const { session, sync, lifecycle } = resolveGitConfig({
1502
+ mode: declaration.mode,
1503
+ options: opts
1504
+ });
1505
+ const state = {
1506
+ url,
1507
+ branch,
1508
+ activeBranch: branch,
1509
+ token: initial.token,
1510
+ session,
1511
+ sync,
1512
+ lifecycle,
1513
+ targetDir,
1514
+ tokenMediator: initial.authRef?.kind === "backend" ? tokenMediator : void 0
1515
+ };
1516
+ const authUrl = this.authUrl(state);
1517
+ await this.prepareCheckout(targetDir, url, branch, authUrl, declaration.access, started);
1518
+ if (session.enabled && !session.isMain && session.slug) {
1519
+ await this.checkoutSessionBranch(state, session, targetDir);
1528
1520
  }
1529
- }
1530
- if (state.managedGitconfig) {
1531
- await this.stripRemoteInlineToken(targetDir);
1532
- } else {
1533
- await this.rebakeRemoteAuth(targetDir, state);
1534
- if (initial.authRef?.kind === "backend" && initial.expiresAt && state.tokenMediator) {
1535
- this.scheduleRefresh(declaration.id, state, initial.expiresAt);
1521
+ if (sync.fetchIntervalSec > 0) {
1522
+ this.startPeriodicFetch(state, targetDir);
1523
+ }
1524
+ if (declaration.mount?.exposeAccessToken === true) {
1525
+ try {
1526
+ this.exposeManagedCredential(declaration, state, url, initial);
1527
+ } catch (err) {
1528
+ this.log.error("managed-gitconfig write failed", err, { connectorId: declaration.id });
1529
+ throw err;
1530
+ }
1531
+ }
1532
+ if (state.managedGitconfig) {
1533
+ await this.stripRemoteInlineToken(targetDir);
1534
+ } else {
1535
+ await this.rebakeRemoteAuth(targetDir, state);
1536
+ if (initial.authRef?.kind === "backend" && initial.expiresAt && state.tokenMediator) {
1537
+ this.scheduleRefresh(declaration.id, state, initial.expiresAt);
1538
+ }
1536
1539
  }
1540
+ this.log.info("mount ok", {
1541
+ targetDir,
1542
+ activeBranch: state.activeBranch,
1543
+ durationMs: Date.now() - started
1544
+ });
1545
+ return this.makeHandle(declaration, state, targetDir);
1546
+ } catch (err) {
1547
+ this.registerUnavailableCredentialHelper(declaration, url, err);
1548
+ throw err;
1537
1549
  }
1538
- this.log.info("mount ok", {
1539
- targetDir,
1540
- activeBranch: state.activeBranch,
1541
- durationMs: Date.now() - started
1542
- });
1543
- return this.makeHandle(declaration, state, targetDir);
1544
1550
  }
1545
1551
  /**
1546
1552
  * Bring `targetDir` into a usable checkout state for `connect()`.
@@ -1994,6 +2000,104 @@ var GitConnector = class extends AbstractConnector {
1994
2000
  }
1995
2001
  return { authRef, token, expiresAt: null };
1996
2002
  }
2003
+ /**
2004
+ * Register a credential helper that *reports* why there is no credential,
2005
+ * after this connector failed to connect.
2006
+ *
2007
+ * Without it git finds no helper for the host and asks the terminal for a
2008
+ * username, so the agent sees `fatal: could not read Username for
2009
+ * 'https://github.com'` — which names neither the mount nor the cause, and
2010
+ * which only ever appears on a write, since reads against a public remote
2011
+ * keep succeeding. The helper written here makes git stop with the mediator's
2012
+ * actual refusal text instead.
2013
+ *
2014
+ * Entirely best-effort: it runs on the failure path and must never mask the
2015
+ * connect error that is already on its way up.
2016
+ */
2017
+ registerUnavailableCredentialHelper(declaration, url, err) {
2018
+ try {
2019
+ if (!url.startsWith("https://") && !url.startsWith("http://")) return;
2020
+ const gitconfigPath = process.env.GIT_CONFIG_GLOBAL;
2021
+ if (!gitconfigPath) return;
2022
+ const skaileDir = dirname(gitconfigPath);
2023
+ const credentialsPath = join(skaileDir, "git-credentials");
2024
+ const urlPrefix = this.deriveUrlPrefix(url);
2025
+ const helperScriptPath = join(skaileDir, `credential-helper-${declaration.id}.sh`);
2026
+ const refreshAttemptPath = join(skaileDir, `refresh-attempt-${declaration.id}`);
2027
+ const reason = redactUrlAuth(err instanceof Error ? err.message : String(err));
2028
+ this.ensureAttemptStamp(declaration.id, refreshAttemptPath);
2029
+ writeHelperScript(
2030
+ helperScriptPath,
2031
+ renderCredentialHelperScript({
2032
+ credentialsPath,
2033
+ refreshFlagPath: join(skaileDir, "refresh-request"),
2034
+ credentialTag: `# skaile-mount: ${declaration.id}`,
2035
+ refreshAttemptPath,
2036
+ unavailableReason: reason
2037
+ })
2038
+ );
2039
+ writeUnavailableMountBlock({
2040
+ mountId: declaration.id,
2041
+ urlPrefix,
2042
+ gitconfigPath,
2043
+ credentialsPath,
2044
+ helperScriptPath
2045
+ });
2046
+ this.log.warn("registered unavailable credential helper for failed mount", {
2047
+ connectorId: declaration.id,
2048
+ urlPrefix
2049
+ });
2050
+ } catch (writeErr) {
2051
+ this.log.warn("failed to register unavailable credential helper", {
2052
+ connectorId: declaration.id,
2053
+ error: writeErr instanceof Error ? writeErr.message : String(writeErr)
2054
+ });
2055
+ }
2056
+ }
2057
+ /**
2058
+ * Best-effort pre-creation of the helper's refresh-attempt stamp. A missing
2059
+ * stamp only costs the cooldown (the helper dances on every git op instead of
2060
+ * once per window), so a failure here is never worth surfacing.
2061
+ */
2062
+ ensureAttemptStamp(connectorId, stampPath) {
2063
+ try {
2064
+ ensureRefreshAttemptStamp(stampPath);
2065
+ } catch (err) {
2066
+ this.log.warn("managed-gitconfig: could not pre-create refresh-attempt stamp", {
2067
+ connectorId,
2068
+ error: err instanceof Error ? err.message : String(err)
2069
+ });
2070
+ }
2071
+ }
2072
+ /**
2073
+ * Best-effort removal of the unavailable-mount blocks that would answer this
2074
+ * connector's git operations: its own from a previous failed session, and any
2075
+ * left under the same URL prefix by a sibling mount that failed earlier in
2076
+ * this `connectAll` (git matches helpers by URL, not by mount id).
2077
+ */
2078
+ clearUnavailableCredentialHelper(connectorId, url) {
2079
+ const gitconfigPath = process.env.GIT_CONFIG_GLOBAL;
2080
+ if (!gitconfigPath) return;
2081
+ let urlPrefix;
2082
+ if (url.startsWith("https://") || url.startsWith("http://")) {
2083
+ try {
2084
+ urlPrefix = this.deriveUrlPrefix(url);
2085
+ } catch {
2086
+ }
2087
+ }
2088
+ try {
2089
+ removeUnavailableMountBlock({
2090
+ mountId: connectorId,
2091
+ gitconfigPath,
2092
+ ...urlPrefix !== void 0 ? { urlPrefix } : {}
2093
+ });
2094
+ } catch (err) {
2095
+ this.log.warn("failed to clear unavailable credential helper", {
2096
+ connectorId,
2097
+ error: err instanceof Error ? err.message : String(err)
2098
+ });
2099
+ }
2100
+ }
1997
2101
  exposeManagedCredential(declaration, state, url, initial) {
1998
2102
  const token = initial.token;
1999
2103
  if (!token) {
@@ -2021,15 +2125,22 @@ var GitConnector = class extends AbstractConnector {
2021
2125
  const urlPrefix = this.deriveUrlPrefix(url);
2022
2126
  const refreshFlagPath = join(skaileDir, "refresh-request");
2023
2127
  const helperScriptPath = join(skaileDir, `credential-helper-${declaration.id}.sh`);
2128
+ const refreshAttemptPath = join(skaileDir, `refresh-attempt-${declaration.id}`);
2024
2129
  const credentialTag2 = `# skaile-mount: ${declaration.id}`;
2025
2130
  const scriptBody = renderCredentialHelperScript({
2026
2131
  credentialsPath,
2027
2132
  refreshFlagPath,
2028
- credentialTag: credentialTag2
2133
+ credentialTag: credentialTag2,
2134
+ // Passed explicitly on both call sites so the failure path and this one
2135
+ // never disagree about which stamp file bounds this mount's dance.
2136
+ refreshAttemptPath
2029
2137
  });
2138
+ this.ensureAttemptStamp(declaration.id, refreshAttemptPath);
2139
+ let helperWritten = true;
2030
2140
  try {
2031
2141
  writeHelperScript(helperScriptPath, scriptBody);
2032
2142
  } catch (err) {
2143
+ helperWritten = false;
2033
2144
  this.log.warn(
2034
2145
  "managed-gitconfig: failed to write credential-helper script -- falling back to static helper",
2035
2146
  {
@@ -2044,7 +2155,12 @@ var GitConnector = class extends AbstractConnector {
2044
2155
  token,
2045
2156
  gitconfigPath,
2046
2157
  credentialsPath,
2047
- helperScriptPath
2158
+ // Only point at the script when it was actually written. Pointing at an
2159
+ // unwritten path is not a fallback: the file may still hold a previous
2160
+ // session's *unavailable* body, which would refuse this live credential
2161
+ // with a stale reason. Omitting it selects the static `store --file=`
2162
+ // form, which reads the credential line this call is about to write.
2163
+ ...helperWritten ? { helperScriptPath } : {}
2048
2164
  });
2049
2165
  state.managedGitconfig = { gitconfigPath, credentialsPath, urlPrefix };
2050
2166
  const providerKind = initial.authRef?.kind ?? "pat";
@@ -5925,6 +6041,13 @@ var ConnectorManager = class {
5925
6041
  }
5926
6042
  opts;
5927
6043
  active = /* @__PURE__ */ new Map();
6044
+ /**
6045
+ * Connectors whose `connect()` threw, keyed by id. A failed connector never
6046
+ * enters `active`, so without this map its failure would exist only in the
6047
+ * log store — invisible to the agent that is about to write into a mount
6048
+ * that will never sync. See {@link listFailedConnectors}.
6049
+ */
6050
+ failedConnectors = /* @__PURE__ */ new Map();
5928
6051
  // Allocated absolute mount target → owning connector id; collisions rejected at setup, released on disconnect.
5929
6052
  mountTargets = /* @__PURE__ */ new Map();
5930
6053
  workspaceDir;
@@ -6063,6 +6186,30 @@ var ConnectorManager = class {
6063
6186
  }
6064
6187
  this.mountTargets.set(absPath, id);
6065
6188
  }
6189
+ /**
6190
+ * Remember a connect failure so the agent can be told about it. Called from
6191
+ * both the bulk (`attemptConnect`) and hot-plug (`connect`) failure paths.
6192
+ */
6193
+ recordFailure(decl, error) {
6194
+ if (this.active.has(decl.id)) return;
6195
+ let mountPath;
6196
+ if (decl.mount != null) {
6197
+ try {
6198
+ mountPath = this.resolveMountTarget(decl);
6199
+ } catch {
6200
+ }
6201
+ }
6202
+ this.failedConnectors.set(decl.id, {
6203
+ id: decl.id,
6204
+ driver: decl.driver,
6205
+ access: decl.access,
6206
+ ...decl.label !== void 0 ? { label: decl.label } : {},
6207
+ ...decl.mount?.source !== void 0 ? { source: decl.mount.source } : {},
6208
+ ...mountPath !== void 0 ? { mountPath } : {},
6209
+ required: decl.access === "read-write" && decl.mount != null,
6210
+ error
6211
+ });
6212
+ }
6066
6213
  /** Release every mount target owned by `id` (on disconnect). */
6067
6214
  releaseMountTarget(id) {
6068
6215
  for (const [path, owner] of [...this.mountTargets]) {
@@ -6215,6 +6362,7 @@ var ConnectorManager = class {
6215
6362
  handle,
6216
6363
  declaration: decl
6217
6364
  });
6365
+ this.failedConnectors.delete(decl.id);
6218
6366
  this.opts.onStatusChange?.({
6219
6367
  type: "connector_status",
6220
6368
  connectorId: decl.id,
@@ -6229,6 +6377,7 @@ var ConnectorManager = class {
6229
6377
  } catch (err) {
6230
6378
  if (!this.active.has(decl.id)) this.releaseMountTarget(decl.id);
6231
6379
  const msg = err instanceof Error ? err.message : String(err);
6380
+ this.recordFailure(decl, msg);
6232
6381
  this.mgrLog.error("connect failed", err, { id: decl.id, driver: decl.driver });
6233
6382
  this.opts.onStatusChange?.({
6234
6383
  type: "connector_status",
@@ -6273,6 +6422,7 @@ var ConnectorManager = class {
6273
6422
  }
6274
6423
  this.active.clear();
6275
6424
  this.mountTargets.clear();
6425
+ this.failedConnectors.clear();
6276
6426
  this.mgrLog.info("disconnectAll complete");
6277
6427
  }
6278
6428
  // ── Hot-plug ──────────────────────────────────────────────────────────────────
@@ -6307,6 +6457,7 @@ var ConnectorManager = class {
6307
6457
  () => connector.connect(declaration, ctx)
6308
6458
  );
6309
6459
  this.active.set(declaration.id, { connector, handle, declaration });
6460
+ this.failedConnectors.delete(declaration.id);
6310
6461
  this.opts.onStatusChange?.({
6311
6462
  type: "connector_status",
6312
6463
  connectorId: declaration.id,
@@ -6316,6 +6467,7 @@ var ConnectorManager = class {
6316
6467
  } catch (err) {
6317
6468
  this.releaseMountTarget(declaration.id);
6318
6469
  const msg = err instanceof Error ? err.message : String(err);
6470
+ this.recordFailure(declaration, msg);
6319
6471
  this.opts.onStatusChange?.({
6320
6472
  type: "connector_status",
6321
6473
  connectorId: declaration.id,
@@ -6328,9 +6480,25 @@ var ConnectorManager = class {
6328
6480
  /**
6329
6481
  * Disconnect a single connector by ID and stop its watcher.
6330
6482
  *
6331
- * @throws {Error} if the connector is not found
6483
+ * A connector that only ever *failed* to connect is not in `active`, so
6484
+ * `get(id)` would throw for it. Removing such a declaration is still a
6485
+ * legitimate host action — and it is the only way to retract its
6486
+ * `## Mount failures` row — so that case is handled as a plain forget rather
6487
+ * than an error.
6488
+ *
6489
+ * @throws {Error} if the connector is neither connected nor recorded as failed
6332
6490
  */
6333
6491
  async disconnect(id) {
6492
+ if (!this.active.has(id) && this.failedConnectors.has(id)) {
6493
+ this.failedConnectors.delete(id);
6494
+ this.releaseMountTarget(id);
6495
+ this.opts.onStatusChange?.({
6496
+ type: "connector_status",
6497
+ connectorId: id,
6498
+ status: "disconnected"
6499
+ });
6500
+ return;
6501
+ }
6334
6502
  const entry = this.get(id);
6335
6503
  if (entry.watchHandle) {
6336
6504
  try {
@@ -6350,6 +6518,7 @@ var ConnectorManager = class {
6350
6518
  await entry.connector.disconnect(entry.handle);
6351
6519
  } finally {
6352
6520
  this.active.delete(id);
6521
+ this.failedConnectors.delete(id);
6353
6522
  this.releaseMountTarget(id);
6354
6523
  this.opts.onStatusChange?.({
6355
6524
  type: "connector_status",
@@ -6465,6 +6634,22 @@ var ConnectorManager = class {
6465
6634
  status: entry.handle.status
6466
6635
  }));
6467
6636
  }
6637
+ /**
6638
+ * Return every connector whose `connect()` threw and that has not since
6639
+ * connected.
6640
+ *
6641
+ * A failed connector is deliberately **absent** from `listConnectors()` and
6642
+ * `get()` — it never entered the active map, and those surfaces describe what
6643
+ * the agent can actually use. That is exactly why this second list exists:
6644
+ * without it a failed mount is invisible to the prompt builder, and the agent
6645
+ * keeps treating a dead directory as a live, synced workspace.
6646
+ *
6647
+ * Entries are cleared by a later successful connect of the same id, by
6648
+ * `disconnect(id)`, and wholesale by `disconnectAll()`.
6649
+ */
6650
+ listFailedConnectors() {
6651
+ return [...this.failedConnectors.values()].map((f) => ({ ...f }));
6652
+ }
6468
6653
  /**
6469
6654
  * Lists git-backed connectors, surfacing the fields prompt-builders and the
6470
6655
  * runner's wake-mid-401 refresh handler need.
@@ -6679,6 +6864,14 @@ var ConnectorManager = class {
6679
6864
  }
6680
6865
  };
6681
6866
  var zStatic = z4.z ?? z4.default ?? z4;
6867
+ function readFailedConnectors(manager) {
6868
+ if (typeof manager.listFailedConnectors !== "function") return [];
6869
+ try {
6870
+ return manager.listFailedConnectors();
6871
+ } catch {
6872
+ return [];
6873
+ }
6874
+ }
6682
6875
  async function buildSdkConnectorTools(manager, options) {
6683
6876
  const shellAccess = options?.shellAccess ?? true;
6684
6877
  let sdk = options?.sdk;
@@ -6693,11 +6886,14 @@ async function buildSdkConnectorTools(manager, options) {
6693
6886
  const tools = [];
6694
6887
  tools.push({
6695
6888
  name: "connector_list",
6696
- description: "List all currently registered connectors with their id, driver, access mode, and available operations. Use this to discover connectors that may have been added after session start (e.g. shared-state stores like 'presence' or 'session' pushed by the host).",
6889
+ description: "List connectors as {connected, failed}. `connected` holds the usable connectors with their id, driver, access mode, and available operations; `failed` holds connectors that did not connect this session, with the failure reason \u2014 their mount paths are dead directories, not synced workspaces. Use this to discover connectors that may have been added after session start (e.g. shared-state stores like 'presence' or 'session' pushed by the host).",
6697
6890
  inputSchema: {},
6698
6891
  handler: async () => {
6699
6892
  try {
6700
- const entries = manager.listConnectors();
6893
+ const entries = {
6894
+ connected: manager.listConnectors(),
6895
+ failed: readFailedConnectors(manager)
6896
+ };
6701
6897
  return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
6702
6898
  } catch (err) {
6703
6899
  return {
@@ -6935,7 +7131,8 @@ function buildOperationInputSchema(args, z5) {
6935
7131
  function buildConnectorPromptSection(manager, options) {
6936
7132
  const shellAccess = options?.shellAccess ?? true;
6937
7133
  const allConnectors = manager.listConnectors();
6938
- if (allConnectors.length === 0) return "";
7134
+ const failed = readFailedConnectors(manager);
7135
+ if (allConnectors.length === 0 && failed.length === 0) return "";
6939
7136
  const sections = [];
6940
7137
  const faces = allConnectors.map((c) => {
6941
7138
  try {
@@ -6953,8 +7150,46 @@ function buildConnectorPromptSection(manager, options) {
6953
7150
  if (toolConnectors.length > 0) {
6954
7151
  sections.push(buildToolSection(manager, toolConnectors, shellAccess));
6955
7152
  }
7153
+ if (failed.length > 0) {
7154
+ sections.unshift(buildFailedConnectorSection(failed));
7155
+ }
6956
7156
  return sections.join("\n\n");
6957
7157
  }
7158
+ function buildFailedConnectorSection(failed) {
7159
+ const lines = [
7160
+ "## Mount failures \u2014 read this first",
7161
+ "",
7162
+ "The following connectors **failed to connect** in this session. Their directories are ordinary local folders: **not** connected, **not** synced, and **not** backend-managed git worktrees. Any other statement in this prompt \u2014 including the host platform's own description of the workspace \u2014 that describes these paths as a managed, synced, or version-controlled workspace **is superseded by this section**.",
7163
+ "",
7164
+ "| Mount | Driver | Path | Access | Required | Failure |",
7165
+ "|---|---|---|---|---|---|"
7166
+ ];
7167
+ for (const f of failed) {
7168
+ const path = f.mountPath ?? f.source ?? "(no mount)";
7169
+ const required = f.required ? "yes" : "no";
7170
+ lines.push(
7171
+ `| ${f.id} | ${f.driver} | \`${path}\` | ${f.access} | ${required} | ${oneLine(f.error)} |`
7172
+ );
7173
+ }
7174
+ lines.push("");
7175
+ lines.push(
7176
+ "**Work written under these paths is not saved.** The mount's lifecycle automation \u2014 auto-commit, push-after-commit, push-on-hibernate, commit-on-hibernate, push-on-close \u2014 is dead for a failed connector: nothing is committed, nothing is pushed, and the contents do not survive the session. Do not tell the user their work is saved, synced, or pushed. If they ask you to write substantial work into one of these paths, say first that it will be lost. `Required: yes` marks a read-write mount the session was supposed to be built on \u2014 that session did not start degraded, it started with nowhere to save anything."
7177
+ );
7178
+ lines.push("");
7179
+ lines.push(
7180
+ "**There is nothing to fix inside this workspace.** The credential for a mount is minted by the host platform, not by anything in here and not by any project setting \u2014 there is no credential field to fill in. Report the failure text above verbatim to the user and ask them to reconnect the connector's provider link on the platform, then restart the session. Do not retry the connection yourself; a connector that failed at startup stays failed until the session restarts."
7181
+ );
7182
+ if (failed.some((f) => f.driver === "git")) {
7183
+ lines.push("");
7184
+ lines.push(
7185
+ "**Git specifically:** `git fetch` and `git ls-remote` can still succeed against a public remote with no credential at all, so a working read is **not** evidence that auth works. Only writes need the credential. `git push` will fail with `skaile: mount not connected: \u2026`. Do not retry it in a loop and do not attempt to configure git credentials by hand."
7186
+ );
7187
+ }
7188
+ return lines.join("\n");
7189
+ }
7190
+ function oneLine(text) {
7191
+ return text.replace(/\s*[\r\n]+\s*/g, " ").replace(/\|/g, "\\|").trim();
7192
+ }
6958
7193
  function buildFilesystemSection(manager, fsConnectors) {
6959
7194
  const lines = [
6960
7195
  "## Mounts",
@@ -7058,15 +7293,29 @@ function safeHost(source) {
7058
7293
  return source;
7059
7294
  }
7060
7295
  }
7061
-
7062
- // connectors/src/credential-helper-script.ts
7296
+ function shQuote(value) {
7297
+ return `'${value.replaceAll("'", "'\\''")}'`;
7298
+ }
7299
+ function singleLine(value) {
7300
+ return value.replace(/[\r\n]+/g, " ").trim();
7301
+ }
7302
+ function mountIdFromTag(tag) {
7303
+ const match = tag.match(/^#\s*skaile-mount:\s*(.+)$/);
7304
+ return (match?.[1] ?? tag).trim();
7305
+ }
7306
+ function stampSlug(tag) {
7307
+ return mountIdFromTag(tag).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "mount";
7308
+ }
7063
7309
  function renderCredentialHelperScript(opts) {
7064
7310
  const triggerAgeSec = opts.triggerAgeSec ?? 300;
7065
7311
  const waitTimeoutMs = opts.waitTimeoutMs ?? 5e3;
7066
7312
  const waitTimeoutSec = Math.max(1, Math.ceil(waitTimeoutMs / 1e3));
7067
- const credPath = opts.credentialsPath;
7068
- const flagPath = opts.refreshFlagPath;
7313
+ const cooldownSec = Math.max(0, Math.trunc(opts.refreshCooldownSec ?? 30));
7069
7314
  const tag = opts.credentialTag;
7315
+ const unavailable = opts.unavailableReason != null && opts.unavailableReason.trim().length > 0;
7316
+ const attemptPath = opts.refreshAttemptPath ?? join(dirname(opts.credentialsPath), `refresh-attempt-${stampSlug(tag)}`);
7317
+ const message = unavailable ? `skaile: mount not connected: ${singleLine(opts.unavailableReason ?? "")}` : `skaile: no credential available for git mount ${mountIdFromTag(tag)} \u2014 the workspace mount is not authenticated.`;
7318
+ const readsStillWorkNote = "skaile: git fetch/ls-remote can still succeed on a public remote; only writes need this credential.";
7070
7319
  return `#!/bin/sh
7071
7320
  # Generated by skaile-runner -- do not edit.
7072
7321
  # Tier-2 git credential helper: serves the latest cached token and triggers a
@@ -7080,15 +7329,29 @@ op="\${1:-}"
7080
7329
  # (none today) also bail silently so we don't break future git versions.
7081
7330
  [ "$op" = "get" ] || exit 0
7082
7331
 
7083
- CRED_FILE='${credPath}'
7084
- FLAG_FILE='${flagPath}'
7085
- TAG='${tag}'
7332
+ CRED_FILE=${shQuote(opts.credentialsPath)}
7333
+ FLAG_FILE=${shQuote(opts.refreshFlagPath)}
7334
+ TAG=${shQuote(tag)}
7335
+ ATTEMPT_FILE=${shQuote(attemptPath)}
7336
+ MSG=${shQuote(message)}
7337
+ UNAVAILABLE=${unavailable ? 1 : 0}
7086
7338
 
7087
7339
  # Drain stdin: git always pipes the request envelope. Leaving it unread can
7088
7340
  # wedge git's helper FD. We don't actually need any of it -- the credentials
7089
7341
  # file already keys by URL prefix via the helper's gitconfig block.
7090
7342
  cat > /dev/null 2>&1 || true
7091
7343
 
7344
+ # 'quit=1' is what actually stops git's helper chain. Exiting 0 with no output
7345
+ # sends git to the next helper and then the terminal prompt ("could not read
7346
+ # Username"); exiting non-zero does the same. Only quit=1 makes git report our
7347
+ # reason as the terminal failure.
7348
+ if [ "$UNAVAILABLE" -eq 1 ]; then
7349
+ printf '%s\\n' "$MSG" >&2
7350
+ printf '%s\\n' ${shQuote(readsStillWorkNote)} >&2
7351
+ printf 'quit=1\\n'
7352
+ exit 0
7353
+ fi
7354
+
7092
7355
  # Cross-platform mtime in epoch seconds: GNU stat -c first (Linux), then BSD
7093
7356
  # stat -f (macOS), then 0 if neither is present (in which case we skip the
7094
7357
  # refresh-flag dance entirely and just serve what we have).
@@ -7106,38 +7369,59 @@ started_empty=0
7106
7369
  [ -s "$CRED_FILE" ] || started_empty=1
7107
7370
 
7108
7371
  if [ "$started_empty" -eq 1 ] || [ "$age" -gt ${triggerAgeSec} ]; then
7109
- # Touch the refresh-flag (atomic open+close emits 'change' to fs.watch).
7110
- : > "$FLAG_FILE" 2>/dev/null || true
7372
+ # Bound the dance. A refresh that keeps failing would otherwise cost every
7373
+ # git network op a fresh timeout, forever. The stamp below is written only
7374
+ # when a dance changed nothing, so a working refresh is never throttled.
7375
+ last_attempt=$(get_mtime "$ATTEMPT_FILE")
7376
+ if [ $((now - last_attempt)) -ge ${cooldownSec} ]; then
7377
+ # Touch the refresh-flag (atomic open+close emits 'change' to fs.watch).
7378
+ # The subshell is load-bearing: ':' is a POSIX *special built-in*, so a
7379
+ # redirection error on it terminates a non-interactive dash/ash outright --
7380
+ # '|| true' never runs, and the helper would die before reaching the
7381
+ # quit=1 block below. Run the redirection in a child instead.
7382
+ ( : > "$FLAG_FILE" ) 2>/dev/null || true
7111
7383
 
7112
- # Poll for the credentials file mtime to advance. usleep gives sub-second
7113
- # resolution where available; sleep 1 is the portable fallback. Resolve which
7114
- # once, up front, rather than probing PATH on every iteration.
7115
- if command -v usleep >/dev/null 2>&1; then
7116
- have_usleep=1
7117
- else
7118
- have_usleep=0
7119
- fi
7120
- end=$((now + ${waitTimeoutSec}))
7121
- while :; do
7122
- cur=$(date +%s 2>/dev/null || echo 0)
7123
- if [ "$cur" -ge "$end" ]; then
7124
- break
7125
- fi
7126
- new_mtime=$(get_mtime "$CRED_FILE")
7127
- if [ "$new_mtime" -gt "$mtime" ]; then
7128
- break
7129
- fi
7130
- # A same-second rewrite doesn't advance the second-resolution mtime, so an
7131
- # empty file that gained content must break on content, not on mtime.
7132
- if [ "$started_empty" -eq 1 ] && [ -s "$CRED_FILE" ]; then
7133
- break
7134
- fi
7135
- if [ "$have_usleep" -eq 1 ]; then
7136
- usleep 200000
7384
+ # Poll for the credentials file mtime to advance. usleep gives sub-second
7385
+ # resolution where available; sleep 1 is the portable fallback. Resolve which
7386
+ # once, up front, rather than probing PATH on every iteration.
7387
+ if command -v usleep >/dev/null 2>&1; then
7388
+ have_usleep=1
7137
7389
  else
7138
- sleep 1
7390
+ have_usleep=0
7391
+ fi
7392
+ refreshed=0
7393
+ end=$((now + ${waitTimeoutSec}))
7394
+ while :; do
7395
+ cur=$(date +%s 2>/dev/null || echo 0)
7396
+ if [ "$cur" -ge "$end" ]; then
7397
+ break
7398
+ fi
7399
+ new_mtime=$(get_mtime "$CRED_FILE")
7400
+ if [ "$new_mtime" -gt "$mtime" ]; then
7401
+ refreshed=1
7402
+ break
7403
+ fi
7404
+ # A same-second rewrite doesn't advance the second-resolution mtime, so an
7405
+ # empty file that gained content must break on content, not on mtime.
7406
+ if [ "$started_empty" -eq 1 ] && [ -s "$CRED_FILE" ]; then
7407
+ refreshed=1
7408
+ break
7409
+ fi
7410
+ if [ "$have_usleep" -eq 1 ]; then
7411
+ usleep 200000
7412
+ else
7413
+ sleep 1
7414
+ fi
7415
+ done
7416
+ if [ "$refreshed" -eq 0 ]; then
7417
+ # Subshell for the same reason as the flag write above. Here it also
7418
+ # matters in the ordinary case: the stamp lives in a runner-owned
7419
+ # directory, so under uid separation the agent can only truncate a file
7420
+ # the runner pre-created. A missing pre-create must cost the cooldown,
7421
+ # never the quit=1 report.
7422
+ ( : > "$ATTEMPT_FILE" ) 2>/dev/null || true
7139
7423
  fi
7140
- done
7424
+ fi
7141
7425
  fi
7142
7426
 
7143
7427
  # Find the credential line for THIS mount (matched by the trailing tag) and
@@ -7146,7 +7430,16 @@ fi
7146
7430
  # more than one matching line (shouldn't happen but defence-in-depth), we
7147
7431
  # take the last one (most recently written).
7148
7432
  line=$(grep -F " $TAG" "$CRED_FILE" 2>/dev/null | tail -n 1)
7149
- [ -n "$line" ] || exit 0
7433
+ if [ -z "$line" ]; then
7434
+ # No credential to serve. Terminate the chain rather than exiting 0, which
7435
+ # would hand git back to its username prompt -- the misleading error this
7436
+ # helper exists to replace. The same fetch-succeeds/push-fails confusion
7437
+ # applies here as on the known-dead path, so say so either way.
7438
+ printf '%s\\n' "$MSG" >&2
7439
+ printf '%s\\n' ${shQuote(readsStillWorkNote)} >&2
7440
+ printf 'quit=1\\n'
7441
+ exit 0
7442
+ fi
7150
7443
 
7151
7444
  # Strip the trailing ' <TAG>' from the URL.
7152
7445
  url=\${line% *}
@@ -7291,6 +7584,7 @@ var ManagedGitconfigCollisionError = class extends Error {
7291
7584
  var blockStart = (mountId) => `# skaile-mount: ${mountId}`;
7292
7585
  var blockEnd = (mountId) => `# skaile-mount: ${mountId} (end)`;
7293
7586
  var credentialTag = (mountId) => `# skaile-mount: ${mountId}`;
7587
+ var UNAVAILABLE_MARKER = "# skaile-mount-unavailable";
7294
7588
  function readFileOrEmpty(path) {
7295
7589
  try {
7296
7590
  return readFileSync(path, "utf-8");
@@ -7395,17 +7689,22 @@ function blockUrlPrefix(block) {
7395
7689
  }
7396
7690
  return void 0;
7397
7691
  }
7692
+ function isUnavailableBlock(block) {
7693
+ return block.lines.some((l) => l.trim() === UNAVAILABLE_MARKER);
7694
+ }
7398
7695
  function writeMountBlock(opts) {
7399
7696
  const { mountId, urlPrefix, token, gitconfigPath, credentialsPath, helperScriptPath } = opts;
7400
7697
  const text = readFileOrEmpty(gitconfigPath);
7401
- const blocks = parseBlocks(text);
7402
- for (const b of blocks) {
7698
+ const parsed = parseBlocks(text);
7699
+ for (const b of parsed) {
7403
7700
  if (b.mountId === "" || b.mountId === mountId) continue;
7701
+ if (isUnavailableBlock(b)) continue;
7404
7702
  const otherPrefix = blockUrlPrefix(b);
7405
7703
  if (otherPrefix === urlPrefix) {
7406
7704
  throw new ManagedGitconfigCollisionError(urlPrefix, b.mountId, mountId);
7407
7705
  }
7408
7706
  }
7707
+ const blocks = parsed.filter((b) => !(isUnavailableBlock(b) && blockUrlPrefix(b) === urlPrefix));
7409
7708
  const newBlock = {
7410
7709
  mountId,
7411
7710
  lines: buildBlock(mountId, urlPrefix, credentialsPath, helperScriptPath)
@@ -7423,6 +7722,48 @@ function writeMountBlock(opts) {
7423
7722
  filtered.push(buildCredentialLine(urlPrefix, token, mountId));
7424
7723
  writeCredentialLines(credentialsPath, filtered);
7425
7724
  }
7725
+ function writeUnavailableMountBlock(opts) {
7726
+ const { mountId, urlPrefix, gitconfigPath, credentialsPath, helperScriptPath } = opts;
7727
+ const lines = readCredentialLines(credentialsPath);
7728
+ const tag = credentialTag(mountId);
7729
+ const filtered = lines.filter((l) => !l.endsWith(tag));
7730
+ if (filtered.length !== lines.length) {
7731
+ writeCredentialLines(credentialsPath, filtered);
7732
+ }
7733
+ const blocks = parseBlocks(readFileOrEmpty(gitconfigPath));
7734
+ for (const b of blocks) {
7735
+ if (b.mountId === "" || b.mountId === mountId) continue;
7736
+ if (isUnavailableBlock(b)) continue;
7737
+ if (blockUrlPrefix(b) === urlPrefix) return;
7738
+ }
7739
+ const newLines = [
7740
+ blockStart(mountId),
7741
+ UNAVAILABLE_MARKER,
7742
+ `[credential "${urlPrefix}"]`,
7743
+ ` helper = !sh ${helperScriptPath}`,
7744
+ blockEnd(mountId)
7745
+ ];
7746
+ const existing = findExistingBlock(blocks, mountId);
7747
+ if (existing) {
7748
+ existing.lines = newLines;
7749
+ } else {
7750
+ blocks.push({ mountId, lines: newLines });
7751
+ }
7752
+ atomicWrite(gitconfigPath, renderBlocks(blocks), 416);
7753
+ }
7754
+ function removeUnavailableMountBlock(opts) {
7755
+ const { mountId, gitconfigPath, urlPrefix } = opts;
7756
+ const text = readFileOrEmpty(gitconfigPath);
7757
+ if (text.length === 0) return;
7758
+ const blocks = parseBlocks(text);
7759
+ const doomed = new Set(
7760
+ blocks.filter(
7761
+ (b) => isUnavailableBlock(b) && (b.mountId === mountId || urlPrefix !== void 0 && blockUrlPrefix(b) === urlPrefix)
7762
+ )
7763
+ );
7764
+ if (doomed.size === 0) return;
7765
+ atomicWrite(gitconfigPath, renderBlocks(blocks.filter((b) => !doomed.has(b))), 416);
7766
+ }
7426
7767
  function removeMountBlock(opts) {
7427
7768
  const { mountId, gitconfigPath, credentialsPath } = opts;
7428
7769
  const text = readFileOrEmpty(gitconfigPath);
@@ -7440,6 +7781,17 @@ function removeMountBlock(opts) {
7440
7781
  function writeHelperScript(scriptPath, body) {
7441
7782
  atomicWrite(scriptPath, body, 493);
7442
7783
  }
7784
+ function ensureRefreshAttemptStamp(stampPath) {
7785
+ if (existsSync(stampPath)) return;
7786
+ const dir = dirname(stampPath);
7787
+ if (!existsSync(dir)) {
7788
+ mkdirSync(dir, { recursive: true, mode: 448 });
7789
+ }
7790
+ const fd = openSync(stampPath, "a", 432);
7791
+ closeSync(fd);
7792
+ chmodSync(stampPath, 432);
7793
+ utimesSync(stampPath, 0, 0);
7794
+ }
7443
7795
  function atomicReplaceCredential(opts) {
7444
7796
  const { mountId, urlPrefix, token, credentialsPath } = opts;
7445
7797
  const lines = readCredentialLines(credentialsPath);
@@ -10200,6 +10552,6 @@ function createConnector() {
10200
10552
  return new FlowAdapter();
10201
10553
  }
10202
10554
 
10203
- export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, BoxConnector, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DeployConnector, DevServerConnector, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, PortPool, PostgresConnector, S3Connector, SQLiteConnector, SessionInlineSubFlowRunner, SessionNodeExecutor, SessionSubpromptRunner, SharePointConnector, StaticServerConnector, TunnelConnector, WebDAVConnector, XStateConnector, XStateStoreConnector, __test, atomicReplaceCredential, buildConnectorPromptSection, buildFlowPromptEvent, buildSdkConnectorTools, buildSdkFlowTools, createConnector, createConnector10, createConnector11, createConnector12, createConnector13, createConnector14, createConnector15, createConnector16, createConnector17, createConnector18, createConnector19, createConnector2, createConnector20, createConnector3, createConnector4, createConnector5, createConnector6, createConnector7, createConnector8, createConnector9, createFsWatcher, createWorktree, defaultSpawn, driveOrchestratorTurn, ensureDirMode, ensureFleetMounted, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, readGitSyncStatus, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
10204
- //# sourceMappingURL=chunk-DGWJAPRC.js.map
10205
- //# sourceMappingURL=chunk-DGWJAPRC.js.map
10555
+ export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, BoxConnector, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DeployConnector, DevServerConnector, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, PortPool, PostgresConnector, S3Connector, SQLiteConnector, SessionInlineSubFlowRunner, SessionNodeExecutor, SessionSubpromptRunner, SharePointConnector, StaticServerConnector, TunnelConnector, WebDAVConnector, XStateConnector, XStateStoreConnector, __test, atomicReplaceCredential, buildConnectorPromptSection, buildFlowPromptEvent, buildSdkConnectorTools, buildSdkFlowTools, createConnector, createConnector10, createConnector11, createConnector12, createConnector13, createConnector14, createConnector15, createConnector16, createConnector17, createConnector18, createConnector19, createConnector2, createConnector20, createConnector3, createConnector4, createConnector5, createConnector6, createConnector7, createConnector8, createConnector9, createFsWatcher, createWorktree, defaultSpawn, driveOrchestratorTurn, ensureDirMode, ensureFleetMounted, ensureRefreshAttemptStamp, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, readGitSyncStatus, registerBuiltinConnectors, removeMountBlock, removeUnavailableMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock, writeUnavailableMountBlock };
10556
+ //# sourceMappingURL=chunk-6GDVGAIZ.js.map
10557
+ //# sourceMappingURL=chunk-6GDVGAIZ.js.map