pikibun 0.23.1 → 0.25.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 (113) hide show
  1. package/dist/chunks/{chunk-NAPYFPGG.js → chunk-3IXKVGAD.js} +3177 -1120
  2. package/dist/chunks/{cli-DRWVFHEJ.js → cli-COW3WXMB.js} +208 -47
  3. package/dist/cli.js +10 -9
  4. package/dist/migrations/postgres/0045_connector_version.sql +6 -0
  5. package/dist/migrations/postgres/0046_session_tunnels.sql +12 -0
  6. package/dist/migrations/postgres/0047_native_page_callback.sql +4 -0
  7. package/dist/migrations/sqlite/0052_connector_version.sql +4 -0
  8. package/dist/migrations/sqlite/0053_session_tunnels.sql +10 -0
  9. package/dist/migrations/sqlite/0054_native_page_callback.sql +4 -0
  10. package/dist/web/app.eb3752a36c.js +14 -0
  11. package/dist/web/app.eb3752a36c.js.br +0 -0
  12. package/dist/web/app.eb3752a36c.js.gz +0 -0
  13. package/dist/web/assets/src/assets/logo.5872d6d67c.png +0 -0
  14. package/dist/web/browser.6333ad295d/LICENSES.txt +83 -0
  15. package/dist/web/browser.6333ad295d/LICENSES.txt.br +0 -0
  16. package/dist/web/browser.6333ad295d/LICENSES.txt.gz +0 -0
  17. package/dist/web/browser.6333ad295d/animation.js +1 -0
  18. package/dist/web/browser.6333ad295d/animation.js.br +0 -0
  19. package/dist/web/browser.6333ad295d/animation.js.gz +0 -0
  20. package/dist/web/browser.6333ad295d/bootstrap.js +1 -0
  21. package/dist/web/browser.6333ad295d/buffer.js +1 -0
  22. package/dist/web/browser.6333ad295d/buffer.js.br +0 -0
  23. package/dist/web/browser.6333ad295d/buffer.js.gz +0 -0
  24. package/dist/web/browser.6333ad295d/canvas.js +1 -0
  25. package/dist/web/browser.6333ad295d/canvas.js.br +0 -0
  26. package/dist/web/browser.6333ad295d/canvas.js.gz +0 -0
  27. package/dist/web/browser.6333ad295d/components.js +1 -0
  28. package/dist/web/browser.6333ad295d/components.js.br +0 -0
  29. package/dist/web/browser.6333ad295d/components.js.gz +0 -0
  30. package/dist/web/browser.6333ad295d/dom.js +1 -0
  31. package/dist/web/browser.6333ad295d/dom.js.br +0 -0
  32. package/dist/web/browser.6333ad295d/dom.js.gz +0 -0
  33. package/dist/web/browser.6333ad295d/environment.js +1 -0
  34. package/dist/web/browser.6333ad295d/environment.js.br +0 -0
  35. package/dist/web/browser.6333ad295d/environment.js.gz +0 -0
  36. package/dist/web/browser.6333ad295d/gesture.js +1 -0
  37. package/dist/web/browser.6333ad295d/gesture.js.br +0 -0
  38. package/dist/web/browser.6333ad295d/gesture.js.gz +0 -0
  39. package/dist/web/browser.6333ad295d/host.js +1 -0
  40. package/dist/web/browser.6333ad295d/host.js.br +0 -0
  41. package/dist/web/browser.6333ad295d/host.js.gz +0 -0
  42. package/dist/web/browser.6333ad295d/hot.js +1 -0
  43. package/dist/web/browser.6333ad295d/hot.js.br +0 -0
  44. package/dist/web/browser.6333ad295d/hot.js.gz +0 -0
  45. package/dist/web/browser.6333ad295d/keymap.js +1 -0
  46. package/dist/web/browser.6333ad295d/keymap.js.br +0 -0
  47. package/dist/web/browser.6333ad295d/keymap.js.gz +0 -0
  48. package/dist/web/browser.6333ad295d/list.js +1 -0
  49. package/dist/web/browser.6333ad295d/list.js.br +0 -0
  50. package/dist/web/browser.6333ad295d/list.js.gz +0 -0
  51. package/dist/web/browser.6333ad295d/loader.js +1 -0
  52. package/dist/web/browser.6333ad295d/modules.js +1 -0
  53. package/dist/web/browser.6333ad295d/modules.js.br +0 -0
  54. package/dist/web/browser.6333ad295d/modules.js.gz +0 -0
  55. package/dist/web/browser.6333ad295d/navigation.js +1 -0
  56. package/dist/web/browser.6333ad295d/navigation.js.br +0 -0
  57. package/dist/web/browser.6333ad295d/navigation.js.gz +0 -0
  58. package/dist/web/browser.6333ad295d/path.js +1 -0
  59. package/dist/web/browser.6333ad295d/path.js.br +0 -0
  60. package/dist/web/browser.6333ad295d/path.js.gz +0 -0
  61. package/dist/web/browser.6333ad295d/reactive.js +1 -0
  62. package/dist/web/browser.6333ad295d/reactive.js.br +0 -0
  63. package/dist/web/browser.6333ad295d/reactive.js.gz +0 -0
  64. package/dist/web/browser.6333ad295d/realm.js +1 -0
  65. package/dist/web/browser.6333ad295d/resource.js +1 -0
  66. package/dist/web/browser.6333ad295d/resource.js.br +0 -0
  67. package/dist/web/browser.6333ad295d/resource.js.gz +0 -0
  68. package/dist/web/browser.6333ad295d/runtime.js +2 -0
  69. package/dist/web/browser.6333ad295d/runtime.js.br +0 -0
  70. package/dist/web/browser.6333ad295d/runtime.js.gz +0 -0
  71. package/dist/web/browser.6333ad295d/scope.js +1 -0
  72. package/dist/web/browser.6333ad295d/scope.js.br +0 -0
  73. package/dist/web/browser.6333ad295d/scope.js.gz +0 -0
  74. package/dist/web/browser.6333ad295d/standard.js +1 -0
  75. package/dist/web/browser.6333ad295d/standard.js.br +0 -0
  76. package/dist/web/browser.6333ad295d/standard.js.gz +0 -0
  77. package/dist/web/browser.6333ad295d/testing.js +1 -0
  78. package/dist/web/browser.6333ad295d/testing.js.br +0 -0
  79. package/dist/web/browser.6333ad295d/testing.js.gz +0 -0
  80. package/dist/web/browser.6333ad295d/values.js +1 -0
  81. package/dist/web/browser.6333ad295d/values.js.br +0 -0
  82. package/dist/web/browser.6333ad295d/values.js.gz +0 -0
  83. package/dist/web/host.19652b6282.js +80 -0
  84. package/dist/web/host.19652b6282.js.br +0 -0
  85. package/dist/web/host.19652b6282.js.gz +0 -0
  86. package/dist/web/icons/180.c8c643a608.png +0 -0
  87. package/dist/web/icons/192.c099d3e1ed.png +0 -0
  88. package/dist/web/icons/32.caf706bbd1.png +0 -0
  89. package/dist/web/icons/512.e2c2dd9ba7.png +0 -0
  90. package/dist/web/index.html +129 -14
  91. package/dist/web/index.html.br +0 -0
  92. package/dist/web/index.html.gz +0 -0
  93. package/dist/web/manifest.webmanifest +22 -0
  94. package/dist/web/nsh-math.7ad3060fbc.wasm +0 -0
  95. package/dist/web/nsh-math.7ad3060fbc.wasm.br +0 -0
  96. package/dist/web/nsh-math.7ad3060fbc.wasm.gz +0 -0
  97. package/dist/web/nsh-math.cc869361ca.bin +0 -0
  98. package/dist/web/nsh-syntaxes.ea0da56d04.bin +0 -0
  99. package/dist/web/nsh-syntaxes.ea0da56d04.bin.br +0 -0
  100. package/dist/web/nsh-syntaxes.ea0da56d04.bin.gz +0 -0
  101. package/dist/web/nsh.5be0579ad8.wasm +0 -0
  102. package/dist/web/nsh.5be0579ad8.wasm.br +0 -0
  103. package/dist/web/nsh.5be0579ad8.wasm.gz +0 -0
  104. package/dist/web/nsh.6b31ba26e0.js +259 -0
  105. package/dist/web/nsh.6b31ba26e0.js.br +0 -0
  106. package/dist/web/nsh.6b31ba26e0.js.gz +0 -0
  107. package/npm-shrinkwrap.json +251 -220
  108. package/package.json +8 -7
  109. package/dist/web/assets/index-BChj4uOJ.js +0 -5
  110. package/dist/web/assets/index-CEoUvOKr.js +0 -64
  111. package/dist/web/assets/index-TpNsUNeA.css +0 -1
  112. package/dist/web/favicon.svg +0 -1
  113. package/dist/web/sw.js +0 -61
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  API_CREDENTIAL_CACHE_SECONDS,
3
3
  COMPACTION_FAILURES,
4
+ CONNECTOR_VERSION_HEADER,
4
5
  ConnectorSetupError,
5
6
  CronError,
6
7
  GITHUB_ISSUE_BODY_LIMIT,
@@ -23,7 +24,9 @@ import {
23
24
  base64Bytes,
24
25
  bindingRemote,
25
26
  boundedStream,
27
+ buildVersion,
26
28
  builtinExtensionNames,
29
+ bundledToolDefinitions,
27
30
  checkBrowseResult,
28
31
  checkPrerequisites,
29
32
  confirmUnsandboxed,
@@ -39,10 +42,13 @@ import {
39
42
  decidedAsHostCommand,
40
43
  defaultHostBrowserConfig,
41
44
  defaultHostConfig,
45
+ defaultHostTunnelConfig,
42
46
  defaultNetworkPolicy,
43
47
  defaultWorkspaceExtensions,
44
48
  documentKind,
45
49
  documentKindNames,
50
+ documentKindsOff,
51
+ documentSourceExtensionOf,
46
52
  drainStream,
47
53
  dropboxItem,
48
54
  egressGitServices,
@@ -100,15 +106,19 @@ import {
100
106
  parseHostCompaction,
101
107
  parseHostConfigChange,
102
108
  parseHostExtensionsReport,
109
+ parseHostTunnelConfig,
103
110
  parseLfsBatchRequest,
104
111
  parseLfsObject,
105
112
  parseNetworkPolicy,
106
113
  parseRequiredExtensions,
114
+ parseSessionTunnelReport,
107
115
  parseUploadChunk,
108
116
  parseWorkspaceExtension,
109
117
  permissionNeeds,
110
118
  permissions,
111
119
  pikiExtension,
120
+ pikiExtensionOn,
121
+ pikiExtensions,
112
122
  pikiExtensionsFor,
113
123
  pikiSessionToolExtensions,
114
124
  plainRepositoryPath,
@@ -157,12 +167,13 @@ import {
157
167
  validRepositoryId,
158
168
  validRepositoryName,
159
169
  validSourceItem,
170
+ validVersion,
160
171
  validateUserEnv,
161
172
  verifyToken,
162
173
  workspaceDatabaseTlsModes,
163
174
  workspaceManagerRoles,
164
175
  workspaceSessionPermissions
165
- } from "./chunk-NAPYFPGG.js";
176
+ } from "./chunk-3IXKVGAD.js";
166
177
  import {
167
178
  ConstraintError,
168
179
  migrationsDirectory,
@@ -566,7 +577,9 @@ function sessionRecord(row, grants) {
566
577
  ...row.tool_output === "brief" ? { toolOutput: "brief" } : {},
567
578
  ...row.parent_session_id ? { parentSessionId: row.parent_session_id } : {},
568
579
  ...row.packed_at ? { packed: true } : {},
569
- ...pullRequest2 ? { pullRequest: pullRequest2 } : {}
580
+ ...pullRequest2 ? { pullRequest: pullRequest2 } : {},
581
+ // An archived session is never online, whatever a host that was away when it was archived said.
582
+ ...row.tunnel_url && row.tunnel_expires_at && row.status !== "archived" && Date.parse(row.tunnel_expires_at) > Date.now() ? { tunnel: { url: row.tunnel_url, expiresAt: row.tunnel_expires_at } } : {}
570
583
  };
571
584
  }
572
585
  function runnerSessionRecord(row) {
@@ -580,6 +593,7 @@ function runnerSessionRecord(row) {
580
593
  nextScheduledAt: _nextScheduledAt,
581
594
  parentSessionId: _parentSessionId,
582
595
  packed: _packed,
596
+ tunnel: _tunnel,
583
597
  ...record4
584
598
  } = sessionRecord(row);
585
599
  return record4;
@@ -705,6 +719,7 @@ var NATIVE_CODE_TTL = 2 * 60 * 1e3;
705
719
  var OPAQUE_TOKEN = /^[A-Za-z0-9_-]{43}$/;
706
720
  var CALLBACK_PATH = "/api/auth/github/callback";
707
721
  var NATIVE_CALLBACK = "pikibun://auth";
722
+ var NATIVE_PAGE_PATH = "/api/auth/native/done";
708
723
  var VERIFIER = /^[A-Za-z0-9_-]{43,128}$/;
709
724
  var APP_STATE = /^[A-Za-z0-9_-]{16,128}$/;
710
725
  var PROFILE_FIELDS = { displayName: true, gitName: true, gitEmail: true, env: true };
@@ -947,8 +962,18 @@ async function githubAuthorization(c, native, inviteTokenHash) {
947
962
  const state = randomToken();
948
963
  const verifier = randomToken();
949
964
  const expiresAt2 = Date.now() + STATE_TTL;
950
- await dbResult(c.env.DB.query(`INSERT INTO oauth_states (state_hash, verifier, expires_at, created_at, native_challenge, native_state, invite_token_hash)
951
- VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(await sha256(state), verifier, expiresAt2, now(), native?.challenge ?? null, native?.state ?? null, inviteTokenHash ?? null).run());
965
+ await dbResult(c.env.DB.query(`INSERT INTO oauth_states
966
+ (state_hash, verifier, expires_at, created_at, native_challenge, native_state, native_callback, invite_token_hash)
967
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).bind(
968
+ await sha256(state),
969
+ verifier,
970
+ expiresAt2,
971
+ now(),
972
+ native?.challenge ?? null,
973
+ native?.state ?? null,
974
+ native?.callback === "page" ? "page" : null,
975
+ inviteTokenHash ?? null
976
+ ).run());
952
977
  const cookieConfig = cookies(c.env);
953
978
  setCookie(c, cookieConfig.state, state, { ...cookieConfig.options, maxAge: STATE_TTL / 1e3, expires: new Date(expiresAt2) });
954
979
  const authorization2 = new URL("https://github.com/login/oauth/authorize");
@@ -996,18 +1021,24 @@ async function githubSignup(c, githubId, inviteTokenHash, native) {
996
1021
  function nativeStart(c) {
997
1022
  const challenges = c.req.queries("challenge");
998
1023
  const states = c.req.queries("state");
1024
+ const callbacks = c.req.queries("callback");
999
1025
  const challenge = challenges?.length === 1 ? challenges[0] : void 0;
1000
1026
  const state = states?.length === 1 ? states[0] : void 0;
1001
1027
  if (!challenge || !OPAQUE_TOKEN.test(challenge) || !state || !APP_STATE.test(state)) fail(400, "invalid_native_request", "Invalid native sign-in request.");
1002
- return githubStart(c, { challenge, state });
1028
+ if (callbacks !== void 0 && (callbacks.length !== 1 || callbacks[0] !== "page")) fail(400, "invalid_native_request", "Invalid native sign-in request.");
1029
+ return githubStart(c, { challenge, state, callback: callbacks ? "page" : "scheme" });
1003
1030
  }
1004
- function nativeCallback(params) {
1005
- return `${NATIVE_CALLBACK}?${new URLSearchParams(params).toString()}`;
1031
+ function nativeCallback(c, native, params) {
1032
+ const query = new URLSearchParams({ ...params, state: native.state }).toString();
1033
+ return native.callback === "page" ? `${appOrigin(c.env)}${NATIVE_PAGE_PATH}?${query}` : `${NATIVE_CALLBACK}?${query}`;
1006
1034
  }
1007
- async function nativeFailure(c, error, state) {
1035
+ async function nativeFailure(c, error, native) {
1008
1036
  if (!(error instanceof HTTPException2)) throw error;
1009
1037
  const body = await error.getResponse().json().catch(() => null);
1010
- return c.redirect(nativeCallback({ error: typeof body?.code === "string" ? body.code : "sign_in_failed", state }));
1038
+ return c.redirect(nativeCallback(c, native, { error: typeof body?.code === "string" ? body.code : "sign_in_failed" }));
1039
+ }
1040
+ function nativeDone(c) {
1041
+ return c.html('<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Pikibun</title><p style="font:16px system-ui,sans-serif;text-align:center;margin-top:30vh">You can return to Pikibun.</p>');
1011
1042
  }
1012
1043
  async function githubUser(c, config, code, verifier, inviteTokenHash = null, native = false) {
1013
1044
  const exchange = await githubJson("https://github.com/login/oauth/access_token", {
@@ -1118,13 +1149,13 @@ async function githubCallback(c) {
1118
1149
  fail(400, "invalid_oauth_state", "Invalid or expired OAuth state.");
1119
1150
  }
1120
1151
  const saved = await dbResult(c.env.DB.query(
1121
- "DELETE FROM oauth_states WHERE state_hash = ? RETURNING verifier, expires_at, native_challenge, native_state, invite_token_hash"
1152
+ "DELETE FROM oauth_states WHERE state_hash = ? RETURNING verifier, expires_at, native_challenge, native_state, native_callback, invite_token_hash"
1122
1153
  ).bind(await sha256(state)).first());
1123
1154
  expireCookie(c, "state");
1124
1155
  if (!saved || !Number.isSafeInteger(saved.expires_at) || saved.expires_at <= Date.now() || !OPAQUE_TOKEN.test(saved.verifier)) {
1125
1156
  fail(400, "invalid_oauth_state", "Invalid or expired OAuth state.");
1126
1157
  }
1127
- const native = saved.native_challenge !== null && saved.native_state !== null ? { challenge: saved.native_challenge, state: saved.native_state } : void 0;
1158
+ const native = saved.native_challenge !== null && saved.native_state !== null ? { challenge: saved.native_challenge, state: saved.native_state, callback: saved.native_callback === "page" ? "page" : "scheme" } : void 0;
1128
1159
  let signedIn;
1129
1160
  try {
1130
1161
  const codes = c.req.queries("code");
@@ -1135,9 +1166,9 @@ async function githubCallback(c) {
1135
1166
  signedIn = await githubUser(c, config, code, saved.verifier, saved.invite_token_hash, !!native);
1136
1167
  } catch (error) {
1137
1168
  if (error instanceof SignupRefusal) {
1138
- return c.redirect(native ? nativeCallback({ error: error.code, state: native.state }) : `${appOrigin(c.env)}/?${new URLSearchParams({ error: error.message }).toString()}`);
1169
+ return c.redirect(native ? nativeCallback(c, native, { error: error.code }) : `${appOrigin(c.env)}/?${new URLSearchParams({ error: error.message }).toString()}`);
1139
1170
  }
1140
- if (native) return nativeFailure(c, error, native.state);
1171
+ if (native) return nativeFailure(c, error, native);
1141
1172
  throw error;
1142
1173
  }
1143
1174
  const { user, tokenStatement } = signedIn;
@@ -1151,9 +1182,9 @@ async function githubCallback(c) {
1151
1182
  c.env.DB.query("INSERT INTO native_auth_codes (code_hash, user_id, challenge, expires_at, created_at) VALUES (?, ?, ?, ?, ?)").bind(await sha256(code), user.id, native.challenge, Date.now() + NATIVE_CODE_TTL, now())
1152
1183
  ]));
1153
1184
  } catch (error) {
1154
- return nativeFailure(c, error, native.state);
1185
+ return nativeFailure(c, error, native);
1155
1186
  }
1156
- return c.redirect(nativeCallback({ code, state: native.state }));
1187
+ return c.redirect(nativeCallback(c, native, { code }));
1157
1188
  }
1158
1189
  const session = await newSession(c.env, user.id, "github");
1159
1190
  await dbResult(c.env.DB.batch([tokenStatement, session.statement]));
@@ -1232,6 +1263,7 @@ function registerAuth(app2) {
1232
1263
  app2.get(CALLBACK_PATH, privateResponse, githubCallback);
1233
1264
  app2.get("/api/auth/local/claim", privateResponse, localClaim);
1234
1265
  app2.get("/api/auth/native/start", privateResponse, nativeStart);
1266
+ app2.get(NATIVE_PAGE_PATH, privateResponse, nativeDone);
1235
1267
  app2.post("/api/auth/native/token", privateResponse, nativeToken);
1236
1268
  app2.post("/api/auth/test", privateResponse, csrfGuard, testLogin);
1237
1269
  app2.get("/api/auth/csrf", privateResponse, authenticate, (c) => c.json({ csrfToken: c.get("auth").csrfToken }));
@@ -1887,7 +1919,8 @@ var actionPermissions = {
1887
1919
  pack: "manage",
1888
1920
  unpack: "manage",
1889
1921
  withdraw: "ask",
1890
- upload: "write"
1922
+ upload: "write",
1923
+ tunnel: "write"
1891
1924
  };
1892
1925
  var connectorPermissions = {
1893
1926
  create: "write",
@@ -1907,7 +1940,8 @@ var connectorPermissions = {
1907
1940
  pack: "manage",
1908
1941
  unpack: "manage",
1909
1942
  withdraw: "ask",
1910
- upload: "write"
1943
+ upload: "write",
1944
+ tunnel: "write"
1911
1945
  };
1912
1946
  var ownerActions = ["create", "restart", "restore", "backup", "fork", "pack", "unpack"];
1913
1947
  function requireArchivedAction(row, action) {
@@ -2056,6 +2090,8 @@ async function authorizeSession(c, connectorId) {
2056
2090
  }
2057
2091
 
2058
2092
  // ../../apps/server/src/authority/operations.ts
2093
+ var programTools = /* @__PURE__ */ new Set([...programToolNames, ...bundledToolDefinitions.filter((item2) => item2.kind === "program").map((item2) => item2.name)]);
2094
+ var knownTools = /* @__PURE__ */ new Set([...toolNames, ...bundledToolDefinitions.map((item2) => item2.name)]);
2059
2095
  function validRepositoryPath(value) {
2060
2096
  return typeof value === "string" && value.length <= 512 && /^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+){1,63}$/.test(value) && !value.split("/").some((part) => [".", "..", ".git"].includes(part.toLowerCase()));
2061
2097
  }
@@ -2096,7 +2132,7 @@ function parseOperation(input2) {
2096
2132
  case "bash":
2097
2133
  return { kind: value.kind, command: text(value.command, "command", 8192) };
2098
2134
  case "program":
2099
- if (Object.keys(value).length !== 3 || !programToolNames.includes(value.tool)) fail(400, "invalid_operation", "Unknown program tool.");
2135
+ if (Object.keys(value).length !== 3 || typeof value.tool !== "string" || !programTools.has(value.tool)) fail(400, "invalid_operation", "Unknown program tool.");
2100
2136
  return { kind: value.kind, tool: value.tool, command: text(value.command, "command", 8192) };
2101
2137
  case "github.comment":
2102
2138
  return { kind: value.kind, repository: githubRepositoryName(value.repository), issue: issueNumber(value.issue), body: text(value.body, "comment body", 6e4) };
@@ -2132,7 +2168,7 @@ function parseOperation(input2) {
2132
2168
  }
2133
2169
  case "permission": {
2134
2170
  const needs = value.needs;
2135
- if (!toolNames.includes(value.tool)) fail(400, "invalid_operation", "Unknown tool.");
2171
+ if (typeof value.tool !== "string" || !knownTools.has(value.tool)) fail(400, "invalid_operation", "Unknown tool.");
2136
2172
  if (!Array.isArray(needs) || !needs.length || needs.join() !== permissionNeeds.filter((need) => needs.includes(need)).join()) {
2137
2173
  fail(400, "invalid_operation", "Invalid permission needs.");
2138
2174
  }
@@ -4862,6 +4898,9 @@ async function sourceRead(c, connectorId) {
4862
4898
  await requireToolCall(c.env, access.row, "source_read", { id: callId, requestedBy: actorId, admission, target: item2 === null ? sourceId : `${sourceId}: ${item2}` });
4863
4899
  const source2 = await c.env.DB.query(`${documentSourceSelect} WHERE d.source_id = ? AND d.workspace_id = ?`).bind(sourceId, access.row.workspace_id).first();
4864
4900
  if (!source2) fail(404, "source_not_found", "This workspace has no such source, or it is a repository (clone those with Git).");
4901
+ if (documentKindsOff(await workspaceExtensions(c.env, access.row.workspace_id)).has(source2.kind)) {
4902
+ fail(403, "source_kind_off", `Reading ${documentKindNames[source2.kind]} sources is turned off in this workspace (${documentSourceExtensionOf(source2.kind)?.source ?? "its extension"}). Its owners and admins turn it on under Settings \u2192 Workspaces \u2192 Extensions.`);
4903
+ }
4865
4904
  return { row: access.row, source: source2 };
4866
4905
  };
4867
4906
  const { row, source } = await authorize();
@@ -8414,6 +8453,7 @@ function connectorPath(action, sessionId, suffix = "") {
8414
8453
  case "unpack":
8415
8454
  case "withdraw":
8416
8455
  case "upload":
8456
+ case "tunnel":
8417
8457
  return `${base}/${action}`;
8418
8458
  default:
8419
8459
  return fail(400, "invalid_action", "Invalid connector action.");
@@ -9370,7 +9410,8 @@ var stringVars = [
9370
9410
  "TENCENT_DOCS_CLIENT_ID",
9371
9411
  "TENCENT_DOCS_CLIENT_SECRET",
9372
9412
  "WEB_PUSH_CONTACT",
9373
- "EXPO_ACCESS_TOKEN"
9413
+ "EXPO_ACCESS_TOKEN",
9414
+ "PIKIBUN_VERSION"
9374
9415
  ];
9375
9416
  var LOOPBACK = ["127.0.0.1", "::1", "localhost"];
9376
9417
  async function loadConfig(processEnv = process.env) {
@@ -9509,7 +9550,8 @@ async function actorRequest(c, actorId, row, action, suffix = "", body, pin) {
9509
9550
  pack: `/v1/sessions/${row.id}/pack`,
9510
9551
  unpack: `/v1/sessions/${row.id}/unpack`,
9511
9552
  withdraw: `/v1/sessions/${row.id}/withdraw`,
9512
- upload: `/v1/sessions/${row.id}/upload`
9553
+ upload: `/v1/sessions/${row.id}/upload`,
9554
+ tunnel: `/v1/sessions/${row.id}/tunnel`
9513
9555
  };
9514
9556
  const serialized = body === void 0 ? "" : JSON.stringify(body);
9515
9557
  const issuedAt = Math.floor(Date.now() / 1e3);
@@ -10061,8 +10103,8 @@ function redactEntry(entry2) {
10061
10103
  // ../../apps/server/src/host-config.ts
10062
10104
  async function connectorHostConfig(env, connectorId) {
10063
10105
  const row = await env.DB.query(`SELECT command_timeout_seconds, gpu, git_credentials, repository_cache, host_sessions, compaction_json,
10064
- builtin_extensions_json, required_extensions_json, browser_json FROM connector_configs WHERE connector_id = ?`).bind(connectorId).first();
10065
- if (!row) return { ...defaultHostConfig, builtinExtensions: [], requiredExtensions: [], browser: { ...defaultHostBrowserConfig } };
10106
+ builtin_extensions_json, required_extensions_json, browser_json, service_lifetime_seconds, tunnel_json FROM connector_configs WHERE connector_id = ?`).bind(connectorId).first();
10107
+ if (!row) return { ...defaultHostConfig, builtinExtensions: [], requiredExtensions: [], browser: { ...defaultHostBrowserConfig }, tunnel: { ...defaultHostTunnelConfig } };
10066
10108
  return {
10067
10109
  commandTimeoutSeconds: Number(row.command_timeout_seconds),
10068
10110
  gpu: Number(row.gpu) === 1,
@@ -10072,9 +10114,23 @@ async function connectorHostConfig(env, connectorId) {
10072
10114
  compaction: storedCompaction(row.compaction_json),
10073
10115
  builtinExtensions: storedBuiltinExtensions(row.builtin_extensions_json),
10074
10116
  requiredExtensions: storedRequiredExtensions(row.required_extensions_json),
10075
- browser: storedBrowser(row.browser_json)
10117
+ browser: storedBrowser(row.browser_json),
10118
+ serviceLifetimeSeconds: storedLifetime(row.service_lifetime_seconds),
10119
+ tunnel: storedTunnel(row.tunnel_json)
10076
10120
  };
10077
10121
  }
10122
+ function storedLifetime(value) {
10123
+ const seconds = Number(value);
10124
+ const { min, max } = hostConfigLimits.serviceLifetimeSeconds;
10125
+ return Number.isSafeInteger(seconds) && seconds >= min && seconds <= max ? seconds : defaultHostConfig.serviceLifetimeSeconds;
10126
+ }
10127
+ function storedTunnel(json2) {
10128
+ try {
10129
+ return parseHostTunnelConfig(JSON.parse(json2));
10130
+ } catch {
10131
+ return { ...defaultHostTunnelConfig };
10132
+ }
10133
+ }
10078
10134
  function storedBrowser(json2) {
10079
10135
  try {
10080
10136
  return parseHostBrowserConfig(JSON.parse(json2));
@@ -10122,12 +10178,14 @@ var columns = Object.freeze({
10122
10178
  compaction: "compaction_json",
10123
10179
  builtinExtensions: "builtin_extensions_json",
10124
10180
  requiredExtensions: "required_extensions_json",
10125
- browser: "browser_json"
10181
+ browser: "browser_json",
10182
+ serviceLifetimeSeconds: "service_lifetime_seconds",
10183
+ tunnel: "tunnel_json"
10126
10184
  });
10127
10185
  function upsert(settings) {
10128
10186
  return `INSERT INTO connector_configs (connector_id, command_timeout_seconds, gpu, git_credentials, repository_cache, host_sessions, compaction_json,
10129
- builtin_extensions_json, required_extensions_json, browser_json, updated_by, updated_at)
10130
- SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? WHERE EXISTS (SELECT 1 FROM connectors WHERE id = ? AND revoked_at IS NULL AND removed_at IS NULL)
10187
+ builtin_extensions_json, required_extensions_json, browser_json, service_lifetime_seconds, tunnel_json, updated_by, updated_at)
10188
+ SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? WHERE EXISTS (SELECT 1 FROM connectors WHERE id = ? AND revoked_at IS NULL AND removed_at IS NULL)
10131
10189
  ON CONFLICT (connector_id) DO UPDATE SET ${settings.map((key) => `${columns[key]} = excluded.${columns[key]}, `).join("")}
10132
10190
  updated_by = excluded.updated_by, updated_at = excluded.updated_at
10133
10191
  RETURNING connector_id`;
@@ -10151,7 +10209,7 @@ function registerHostConfig(app2) {
10151
10209
  let settings = Object.keys(change);
10152
10210
  if (row.role !== "owner") {
10153
10211
  if (settings.some((key) => ownerOnlyHostConfig.includes(key) && JSON.stringify(change[key]) !== JSON.stringify(current[key]))) {
10154
- fail(403, "host_owner_required", "Only the host's owner decides whether it uses its Git credentials, keeps a repository cache, runs sessions without the sandbox, requires extensions of every session on it or where its browser comes from.");
10212
+ fail(403, "host_owner_required", "Only the host's owner decides whether it uses its Git credentials, keeps a repository cache, runs sessions without the sandbox, requires extensions of every session on it, where its browser comes from or whether its sessions may put servers online through tunnels.");
10155
10213
  }
10156
10214
  settings = settings.filter((key) => !ownerOnlyHostConfig.includes(key));
10157
10215
  if (!settings.length) return c.json({ config: current });
@@ -10168,6 +10226,8 @@ function registerHostConfig(app2) {
10168
10226
  JSON.stringify(config.builtinExtensions),
10169
10227
  JSON.stringify(config.requiredExtensions),
10170
10228
  JSON.stringify(config.browser),
10229
+ config.serviceLifetimeSeconds,
10230
+ JSON.stringify(config.tunnel),
10171
10231
  actorId,
10172
10232
  now(),
10173
10233
  row.id
@@ -10388,6 +10448,7 @@ async function versionBatch(env, statements, versionIndex) {
10388
10448
  throw error;
10389
10449
  }
10390
10450
  }
10451
+ var agentToolNames = [...toolNames, ...bundledToolDefinitions.map((item2) => item2.name).filter((name2) => !toolNames.includes(name2))];
10391
10452
  function carries(capabilities, tool) {
10392
10453
  if (retiredToolNames.includes(tool)) return false;
10393
10454
  if (tool === "git_push") return capabilities.gitPush === true;
@@ -10401,7 +10462,7 @@ function carries(capabilities, tool) {
10401
10462
  async function chatAgentVersion(env, ownerId, model, capabilities) {
10402
10463
  const agentId = id();
10403
10464
  const createdAt = now();
10404
- const tools = toolNames.filter((tool) => carries(capabilities, tool));
10465
+ const tools = agentToolNames.filter((tool) => carries(capabilities, tool));
10405
10466
  const input2 = { name: model.name, description: "", instructions: "", toolsJson: JSON.stringify(tools), modelId: model.id };
10406
10467
  return versionBatch(env, [
10407
10468
  env.DB.query(`INSERT INTO agents (id, author_id, created_at, chat)
@@ -10415,7 +10476,7 @@ async function migratedAgentVersion(env, ownerId, source, model, capabilities) {
10415
10476
  const agentId = id();
10416
10477
  const createdAt = now();
10417
10478
  const wanted = JSON.parse(source.tools_json);
10418
- const tools = toolNames.filter((tool) => wanted.includes(tool) && carries(capabilities, tool));
10479
+ const tools = agentToolNames.filter((tool) => wanted.includes(tool) && carries(capabilities, tool));
10419
10480
  const input2 = {
10420
10481
  name: source.name,
10421
10482
  description: source.description,
@@ -10980,6 +11041,40 @@ function redactedEntry(entry2) {
10980
11041
  return shown;
10981
11042
  }
10982
11043
 
11044
+ // ../../apps/server/src/session-tunnels.ts
11045
+ async function reportSessionTunnel(c, connectorId) {
11046
+ let report;
11047
+ try {
11048
+ report = parseSessionTunnelReport(await jsonObject(c, 4096));
11049
+ } catch (error) {
11050
+ return fail(400, "invalid_tunnel", error instanceof Error ? error.message : "Invalid tunnel report.");
11051
+ }
11052
+ const row = await c.env.DB.query("SELECT * FROM sessions WHERE id = ?").bind(report.sessionId).first();
11053
+ if (!row) fail(404, "not_found", "Session not found.");
11054
+ await requireCallbackScope(c, row, connectorId);
11055
+ const actor = report.actorId && await c.env.DB.query("SELECT 1 FROM workspace_members WHERE workspace_id = ? AND user_id = ?").bind(row.workspace_id, report.actorId).first() ? report.actorId : null;
11056
+ const detail = { connectorId, url: report.url, ...report.expiresAt ? { expiresAt: report.expiresAt } : {}, ...report.reason ? { reason: report.reason } : {} };
11057
+ await c.env.DB.batch([
11058
+ report.event === "started" ? c.env.DB.query("UPDATE sessions SET tunnel_url = ?, tunnel_expires_at = ? WHERE id = ? AND connector_id = ? AND status <> 'archived'").bind(report.url, report.expiresAt, row.id, connectorId) : c.env.DB.query("UPDATE sessions SET tunnel_url = NULL, tunnel_expires_at = NULL WHERE id = ? AND connector_id = ? AND tunnel_url = ?").bind(row.id, connectorId, report.url),
11059
+ auditStatement(c.env, actor, `session.tunnel.${report.event}`, row.workspace_id, row.id, detail)
11060
+ ]);
11061
+ invalidateSessionView(c, row.id);
11062
+ return c.json({ ok: true });
11063
+ }
11064
+ async function stopSessionTunnel(c, row, reason) {
11065
+ const result3 = await runnerJSON(await runnerRequest(c, row, "tunnel", "", { action: "stop", ...reason ? { reason } : {} }));
11066
+ invalidateSessionView(c, row.id);
11067
+ if (typeof result3?.stopped !== "boolean") fail(502, "invalid_runner_response", "The runner returned an invalid tunnel result.");
11068
+ return result3.stopped;
11069
+ }
11070
+ function registerSessionTunnels(app2) {
11071
+ app2.post("/api/sessions/:id/tunnel/stop", async (c) => {
11072
+ const { row } = await requireSession(c, identifier(c.req.param("id")), "write");
11073
+ if (!row.connector_id) fail(409, "tunnel_unsupported", "Only sessions on a connected host put servers online.");
11074
+ return c.json({ stopped: await stopSessionTunnel(c, row) });
11075
+ });
11076
+ }
11077
+
10983
11078
  // ../../apps/server/src/transcript-settings.ts
10984
11079
  var adminSql6 = `EXISTS (SELECT 1 FROM workspace_members WHERE workspace_id = ? AND user_id = ? AND role IN ('owner','admin'))`;
10985
11080
  function settingsOf2(row) {
@@ -11358,7 +11453,9 @@ async function archiveSession(c, sessionId, audit = {}) {
11358
11453
  const access = await requireSessionManager(c, sessionId, "manage", "Only the session owner, or the workspace's owner or an admin, can archive it.");
11359
11454
  const { row } = access;
11360
11455
  const actorId = c.get("auth").userId;
11361
- const archived = await c.env.DB.query(`UPDATE sessions SET archived_status = status, status = 'archived', policy_version = policy_version + 1
11456
+ if (row.tunnel_url && row.connector_id) await stopSessionTunnel(c, row, "archived").catch(() => false);
11457
+ const archived = await c.env.DB.query(`UPDATE sessions SET archived_status = status, status = 'archived', policy_version = policy_version + 1,
11458
+ tunnel_url = NULL, tunnel_expires_at = NULL
11362
11459
  WHERE id = ? AND owner_id = ? AND policy_version = ? AND status <> 'archived' AND ${sessionManagerSQL("sessions")}
11363
11460
  RETURNING *`).bind(row.id, row.owner_id, row.policy_version, actorId).first();
11364
11461
  if (!archived) fail(409, "policy_changed", "Session status or permissions changed; reload and retry.");
@@ -14335,6 +14432,7 @@ async function connectorInfo(env, row, viewer) {
14335
14432
  revokedAt: row.revoked_at,
14336
14433
  online,
14337
14434
  models,
14435
+ ...row.connector_version ? { version: row.connector_version } : {},
14338
14436
  workspaceIds: workspaceIds.filter(visible),
14339
14437
  connectedWorkspaceIds: connectedWorkspaceIds.filter(visible),
14340
14438
  capabilities,
@@ -14444,8 +14542,9 @@ function registerConnectorCredentials(app2) {
14444
14542
  const input2 = await jsonObject(c, 128 * 1024);
14445
14543
  onlyFields2(input2, ["models", "capabilities"]);
14446
14544
  const manifest = withHostToolCapabilities(input2, (base) => parseManifest(base, c.env));
14447
- const updated = await c.env.DB.query(`UPDATE connectors SET models_json = ?, capabilities_json = ? WHERE id = ?
14448
- AND revoked_at IS NULL AND connection_hash = ? RETURNING *`).bind(JSON.stringify(manifest.models), JSON.stringify(manifest.capabilities), row.id, row.connection_hash).first();
14545
+ const version = validVersion(c.req.header(CONNECTOR_VERSION_HEADER)) ?? null;
14546
+ const updated = await c.env.DB.query(`UPDATE connectors SET models_json = ?, capabilities_json = ?, connector_version = ? WHERE id = ?
14547
+ AND revoked_at IS NULL AND connection_hash = ? RETURNING *`).bind(JSON.stringify(manifest.models), JSON.stringify(manifest.capabilities), version, row.id, row.connection_hash).first();
14449
14548
  if (!updated) fail(401, "stale_connection", "Connection has been replaced.");
14450
14549
  await syncModels(c.env, updated, null);
14451
14550
  return c.json({ ok: true });
@@ -14453,11 +14552,16 @@ function registerConnectorCredentials(app2) {
14453
14552
  app2.post("/api/connectors/self/authorize", async (c) => authorizeSession(c, (await authenticateConnector(c)).id));
14454
14553
  app2.post("/api/connectors/self/activity", async (c) => reportSessionActivity(c, (await authenticateConnector(c)).id));
14455
14554
  app2.post("/api/connectors/self/branches", async (c) => reportSessionBranches(c, (await authenticateConnector(c)).id));
14555
+ app2.post("/api/connectors/self/tunnel", async (c) => reportSessionTunnel(c, (await authenticateConnector(c)).id));
14456
14556
  app2.post("/api/connectors/self/github/comment", async (c) => githubComment(c, (await authenticateConnector(c)).id));
14457
14557
  app2.post("/api/connectors/self/github/read", async (c) => githubRead(c, (await authenticateConnector(c)).id));
14458
14558
  app2.post("/api/connectors/self/github/merge", async (c) => githubMerge(c, (await authenticateConnector(c)).id));
14459
14559
  app2.post("/api/connectors/self/github/issue", async (c) => githubIssue(c, (await authenticateConnector(c)).id));
14460
14560
  app2.post("/api/connectors/self/sources/read", async (c) => sourceRead(c, (await authenticateConnector(c)).id));
14561
+ app2.post("/api/connectors/self/tools", async (c) => {
14562
+ await authenticateConnector(c);
14563
+ return c.json({ tools: bundledToolDefinitions });
14564
+ });
14461
14565
  app2.post("/api/connectors/self/schedules", async (c) => agentSchedules(c, (await authenticateConnector(c)).id));
14462
14566
  app2.post("/api/connectors/self/sessions", async (c) => agentSessions(c, (await authenticateConnector(c)).id));
14463
14567
  app2.post("/api/connectors/self/web/search", async (c) => {
@@ -14592,7 +14696,7 @@ function registerConnectors(app2) {
14592
14696
  const timestamp2 = now();
14593
14697
  await c.env.DB.batch([
14594
14698
  c.env.DB.query(`UPDATE connectors SET removed_at = ?, revoked_at = COALESCE(revoked_at, ?),
14595
- connection_hash = NULL, credential_hash = ?, name = 'Removed host', models_json = '[]', capabilities_json = '{}'
14699
+ connection_hash = NULL, credential_hash = ?, name = 'Removed host', models_json = '[]', capabilities_json = '{}', connector_version = NULL
14596
14700
  WHERE id = ?`).bind(timestamp2, timestamp2, await sha256(randomToken()), row.id),
14597
14701
  c.env.DB.query("DELETE FROM connector_shares WHERE connector_id = ?").bind(row.id),
14598
14702
  c.env.DB.query("DELETE FROM connector_workspaces WHERE connector_id = ?").bind(row.id),
@@ -14929,14 +15033,31 @@ function availability(extensions, installed) {
14929
15033
  function shownReport(report, ownerView) {
14930
15034
  if (ownerView) return report;
14931
15035
  const browser = report.browser && (({ executable: _, error: __, ...rest }) => rest)(report.browser);
14932
- return { ...report, installed: report.installed.filter((item2) => !item2.dev), ...browser ? { browser } : {} };
15036
+ const tunnel = report.tunnel && (({ executable: _, error: __, ...rest }) => rest)(report.tunnel);
15037
+ return { ...report, installed: report.installed.filter((item2) => !item2.dev), ...browser ? { browser } : {}, ...tunnel ? { tunnel } : {} };
14933
15038
  }
14934
15039
  var coreSources = pikiSessionToolExtensions().filter((item2) => item2.core).map((item2) => item2.source);
14935
15040
  var adminSql8 = `EXISTS (SELECT 1 FROM workspace_members WHERE workspace_id = ? AND user_id = ? AND role IN ('owner','admin'))`;
14936
- function connectorWorkspaceExtensions(extensions, installed) {
15041
+ function connectorWorkspaceExtensions(extensions, installed, options = {}) {
14937
15042
  const has = new Set((installed ?? []).map((item2) => item2.source));
14938
15043
  const knowsPiki = [...has].some(isPikiExtensionSource);
14939
- return extensions.filter((item2) => !isPikiExtensionSource(item2.source) || knowsPiki && (!sessionToolSources.includes(item2.source) || has.has(item2.source)));
15044
+ if (options.toolDefinitions && knowsPiki) for (const definition of bundledToolDefinitions) for (const extension of definition.extensions) has.add(extension);
15045
+ const shown = extensions.filter((item2) => !isPikiExtensionSource(item2.source) || knowsPiki && (!sessionToolSources.includes(item2.source) || has.has(item2.source)));
15046
+ return knowsPiki ? withReplacedSwitches(shown, extensions, has) : shown;
15047
+ }
15048
+ function withReplacedSwitches(shown, extensions, has) {
15049
+ let result3 = [...shown];
15050
+ const replaced = new Set(pikiExtensions.flatMap((item2) => item2.replaces ? [item2.replaces] : []));
15051
+ for (const source of replaced) {
15052
+ const successors = pikiExtensions.filter((item2) => item2.replaces === source);
15053
+ if (!has.has(source) || successors.some((item2) => has.has(item2.source))) continue;
15054
+ const on = successors.some((item2) => pikiExtensionOn(item2, extensions));
15055
+ const own = result3.find((item2) => item2.source === source);
15056
+ if (on) result3 = result3.filter((item2) => item2.source !== source);
15057
+ else if (own) result3 = result3.map((item2) => item2.source === source ? { ...item2, enabled: false } : item2);
15058
+ else result3.push({ source, enabled: false, tools: "approval", settings: {} });
15059
+ }
15060
+ return result3;
14940
15061
  }
14941
15062
  function registerConnectorExtensions(app2) {
14942
15063
  app2.post("/api/connectors/self/extensions", async (c) => {
@@ -14977,12 +15098,13 @@ function registerConnectorExtensions(app2) {
14977
15098
  const row = await authenticateConnector(c);
14978
15099
  const input2 = await jsonObject(c, 4096);
14979
15100
  const workspaceId = identifier(input2.workspaceId, "workspaceId");
15101
+ const options = { toolDefinitions: input2.toolDefinitions === true };
14980
15102
  const connected = await c.env.DB.query(`SELECT cw.connector_id FROM connector_workspaces cw
14981
15103
  JOIN workspace_members m ON m.workspace_id = cw.workspace_id AND m.user_id = ?
14982
15104
  WHERE cw.connector_id = ? AND cw.workspace_id = ?`).bind(row.owner_id, row.id, workspaceId).first();
14983
15105
  if (!connected) fail(404, "not_found", "Workspace not found.");
14984
15106
  const extensions = await workspaceExtensions(c.env, workspaceId);
14985
- const shown = extensions.some((item2) => isPikiExtensionSource(item2.source)) ? connectorWorkspaceExtensions(extensions, (await hostReport(c.env, row.id))?.report.installed) : extensions;
15107
+ const shown = extensions.some((item2) => isPikiExtensionSource(item2.source)) ? connectorWorkspaceExtensions(extensions, (await hostReport(c.env, row.id))?.report.installed, options) : extensions;
14986
15108
  return c.json({ extensions: shown, automaticTools: await automaticTools(c.env, row.id) });
14987
15109
  });
14988
15110
  }
@@ -16002,6 +16124,8 @@ app.get("/api/health", async (c) => {
16002
16124
  }
16003
16125
  return c.json({
16004
16126
  ok: true,
16127
+ // The release it runs, such as 0.24.0; null in development. Shown in the web app's settings.
16128
+ version: validVersion(c.env.PIKIBUN_VERSION) ?? buildVersion ?? null,
16005
16129
  githubConfigured: !!c.env.GITHUB_CLIENT_ID && !!c.env.GITHUB_CLIENT_SECRET && vaultReady,
16006
16130
  runnerConfigured: !!c.env.RUNNER_URL && !!c.env.RUNNER_SHARED_SECRET && c.env.RUNNER_SHARED_SECRET.length >= 32,
16007
16131
  databaseConfigured: true,
@@ -16045,6 +16169,7 @@ registerSessions(app);
16045
16169
  registerSessionLinks(app);
16046
16170
  registerBackups(app);
16047
16171
  registerSessionFiles(app);
16172
+ registerSessionTunnels(app);
16048
16173
  registerFileUploads(app);
16049
16174
  registerScheduledPrompts(app);
16050
16175
  registerCrossSession(app);
@@ -16062,7 +16187,7 @@ app.all("/api/*", () => fail(404, "not_found", "API route not found."));
16062
16187
 
16063
16188
  // ../../apps/server/src/static.ts
16064
16189
  import { createReadStream } from "node:fs";
16065
- import { stat } from "node:fs/promises";
16190
+ import { readFile as readFile3, stat } from "node:fs/promises";
16066
16191
  import { extname, join as join2, resolve as resolve3, sep as sep2 } from "node:path";
16067
16192
  import { Readable } from "node:stream";
16068
16193
  var contentTypes = {
@@ -16070,9 +16195,11 @@ var contentTypes = {
16070
16195
  ".js": "text/javascript; charset=utf-8",
16071
16196
  ".mjs": "text/javascript; charset=utf-8",
16072
16197
  ".css": "text/css; charset=utf-8",
16198
+ ".wasm": "application/wasm",
16073
16199
  ".json": "application/json; charset=utf-8",
16074
16200
  ".webmanifest": "application/manifest+json",
16075
16201
  ".map": "application/json",
16202
+ ".bin": "application/octet-stream",
16076
16203
  ".svg": "image/svg+xml",
16077
16204
  ".png": "image/png",
16078
16205
  ".jpg": "image/jpeg",
@@ -16083,12 +16210,30 @@ var contentTypes = {
16083
16210
  ".woff": "font/woff",
16084
16211
  ".woff2": "font/woff2",
16085
16212
  ".ttf": "font/ttf",
16213
+ ".otf": "font/otf",
16086
16214
  ".txt": "text/plain; charset=utf-8"
16087
16215
  };
16216
+ var hashedName = /\.[0-9a-f]{10}\.[a-z0-9]+$/;
16217
+ function pageContentSecurityPolicy() {
16218
+ return [
16219
+ "default-src 'self'",
16220
+ "script-src 'self' blob: 'unsafe-inline' 'wasm-unsafe-eval'",
16221
+ "style-src 'self' 'unsafe-inline'",
16222
+ "img-src 'self' data: blob: https://avatars.githubusercontent.com",
16223
+ "font-src 'self' data:",
16224
+ // The runtime reads its own blobs (a pasted image, a picked photo) with fetch.
16225
+ "connect-src 'self' blob: data:",
16226
+ "worker-src 'self' blob:",
16227
+ "object-src 'none'",
16228
+ "base-uri 'self'",
16229
+ "frame-ancestors 'none'",
16230
+ "form-action 'self'"
16231
+ ].join("; ");
16232
+ }
16088
16233
  function createStaticHandler(root) {
16089
16234
  const base = resolve3(root);
16090
16235
  const index = join2(base, "index.html");
16091
- async function serve(path2, request2, cacheControl) {
16236
+ async function file2(path2, request2) {
16092
16237
  let info2;
16093
16238
  try {
16094
16239
  info2 = await stat(path2);
@@ -16100,11 +16245,24 @@ function createStaticHandler(root) {
16100
16245
  "Content-Type": contentTypes[extname(path2).toLowerCase()] ?? "application/octet-stream",
16101
16246
  "Content-Length": String(info2.size),
16102
16247
  "Last-Modified": info2.mtime.toUTCString(),
16103
- "Cache-Control": cacheControl
16248
+ "Cache-Control": hashedName.test(path2) ? "public, max-age=31536000, immutable" : "no-cache"
16104
16249
  };
16105
16250
  if (request2.method === "HEAD") return new Response(null, { status: 200, headers: headers3 });
16106
16251
  return new Response(Readable.toWeb(createReadStream(path2)), { status: 200, headers: headers3 });
16107
16252
  }
16253
+ async function page(request2) {
16254
+ let html;
16255
+ try {
16256
+ html = await readFile3(index, "utf8");
16257
+ } catch {
16258
+ return Response.json({ error: "The web app is not built: run `pnpm build`.", code: "not_found" }, { status: 404 });
16259
+ }
16260
+ if (!/<base\s/i.test(html)) html = html.replace(/<head>/i, '<head>\n<base href="/">');
16261
+ return new Response(request2.method === "HEAD" ? null : html, {
16262
+ status: 200,
16263
+ headers: { "Content-Type": contentTypes[".html"], "Cache-Control": "no-cache", "Content-Security-Policy": pageContentSecurityPolicy() }
16264
+ });
16265
+ }
16108
16266
  return async (request2) => {
16109
16267
  let pathname;
16110
16268
  try {
@@ -16114,12 +16272,12 @@ function createStaticHandler(root) {
16114
16272
  }
16115
16273
  if (!pathname.includes("\0") && pathname !== "/") {
16116
16274
  const path2 = resolve3(base, `.${pathname}`);
16117
- if (path2.startsWith(base + sep2)) {
16118
- const response = await serve(path2, request2, pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "no-cache");
16275
+ if (path2.startsWith(base + sep2) && path2 !== index) {
16276
+ const response = await file2(path2, request2);
16119
16277
  if (response) return response;
16120
16278
  }
16121
16279
  }
16122
- return await serve(index, request2, "no-cache") ?? Response.json({ error: "Not found.", code: "not_found" }, { status: 404 });
16280
+ return page(request2);
16123
16281
  };
16124
16282
  }
16125
16283
 
@@ -16184,8 +16342,11 @@ async function startServer(config) {
16184
16342
  if (c.req.method !== "GET" && c.req.method !== "HEAD" || c.req.path.startsWith("/api/")) {
16185
16343
  return c.json({ error: "Not found.", code: "not_found" }, 404);
16186
16344
  }
16187
- const response = await serveStatic(c.req.raw);
16188
- return c.newResponse(response.body, response);
16345
+ const served = await serveStatic(c.req.raw);
16346
+ const response = c.newResponse(served.body, served);
16347
+ const policy = served.headers.get("Content-Security-Policy");
16348
+ if (policy) response.headers.set("Content-Security-Policy", policy);
16349
+ return response;
16189
16350
  });
16190
16351
  const server = createAdaptorServer({
16191
16352
  overrideGlobalObjects: false,
@@ -16435,7 +16596,7 @@ function defaultStaticDir() {
16435
16596
  let directory = dirname3(fileURLToPath(import.meta.url));
16436
16597
  for (let depth = 0; depth < 6; depth++) {
16437
16598
  for (const candidate of [join4(directory, "web"), join4(directory, "dist", "web")]) {
16438
- if (existsSync(join4(candidate, "index.html")) && existsSync(join4(candidate, "assets"))) return candidate;
16599
+ if (existsSync(join4(candidate, "index.html")) && existsSync(join4(candidate, "manifest.webmanifest"))) return candidate;
16439
16600
  }
16440
16601
  directory = dirname3(directory);
16441
16602
  }