@schoolai/shipyard-mcp 0.2.2-next.483 → 0.2.2-next.487

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.
@@ -28494,7 +28494,7 @@ init_cjs_shims();
28494
28494
  // ../../packages/schema/dist/index.mjs
28495
28495
  init_cjs_shims();
28496
28496
 
28497
- // ../../packages/schema/dist/yjs-helpers-BBG4tC2R.mjs
28497
+ // ../../packages/schema/dist/yjs-helpers-A0hIPiRs.mjs
28498
28498
  init_cjs_shims();
28499
28499
 
28500
28500
  // ../../packages/schema/dist/plan.mjs
@@ -42552,7 +42552,8 @@ var PlanEventSchema = external_exports.discriminatedUnion("type", [
42552
42552
  PlanEventBaseSchema.extend({
42553
42553
  type: external_exports.literal("agent_activity"),
42554
42554
  data: AgentActivityDataSchema
42555
- })
42555
+ }),
42556
+ PlanEventBaseSchema.extend({ type: external_exports.literal("session_token_regenerated") })
42556
42557
  ]);
42557
42558
  var PlanMetadataBaseSchema = external_exports.object({
42558
42559
  id: external_exports.string(),
@@ -42665,7 +42666,7 @@ var PRReviewCommentSchema = external_exports.object({
42665
42666
  resolved: external_exports.boolean().optional()
42666
42667
  });
42667
42668
 
42668
- // ../../packages/schema/dist/yjs-helpers-BBG4tC2R.mjs
42669
+ // ../../packages/schema/dist/yjs-helpers-A0hIPiRs.mjs
42669
42670
  function assertNever2(value) {
42670
42671
  throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
42671
42672
  }
@@ -43792,6 +43793,29 @@ var GitHubPRResponseSchema = external_exports.object({
43792
43793
  merged: external_exports.boolean(),
43793
43794
  head: external_exports.object({ ref: external_exports.string() })
43794
43795
  });
43796
+ var ROUTES = {
43797
+ REGISTRY_LIST: "/registry",
43798
+ REGISTRY_REGISTER: "/register",
43799
+ REGISTRY_UNREGISTER: "/unregister",
43800
+ PLAN_STATUS: (planId) => `/api/plan/${planId}/status`,
43801
+ PLAN_HAS_CONNECTIONS: (planId) => `/api/plan/${planId}/has-connections`,
43802
+ PLAN_TRANSCRIPT: (planId) => `/api/plan/${planId}/transcript`,
43803
+ PLAN_SUBSCRIBE: (planId) => `/api/plan/${planId}/subscribe`,
43804
+ PLAN_CHANGES: (planId) => `/api/plan/${planId}/changes`,
43805
+ PLAN_UNSUBSCRIBE: (planId) => `/api/plan/${planId}/unsubscribe`,
43806
+ PLAN_PR_DIFF: (planId, prNumber) => `/api/plans/${planId}/pr-diff/${prNumber}`,
43807
+ PLAN_PR_FILES: (planId, prNumber) => `/api/plans/${planId}/pr-files/${prNumber}`,
43808
+ HOOK_SESSION: "/api/hook/session",
43809
+ HOOK_CONTENT: (planId) => `/api/hook/plan/${planId}/content`,
43810
+ HOOK_REVIEW: (planId) => `/api/hook/plan/${planId}/review`,
43811
+ HOOK_SESSION_TOKEN: (planId) => `/api/hook/plan/${planId}/session-token`,
43812
+ HOOK_PRESENCE: (planId) => `/api/hook/plan/${planId}/presence`,
43813
+ CONVERSATION_IMPORT: "/api/conversation/import",
43814
+ WEB_TASK: (planId) => `/task/${planId}`
43815
+ };
43816
+ function createPlanWebUrl(baseUrl, planId) {
43817
+ return `${baseUrl.replace(/\/$/, "")}${ROUTES.WEB_TASK(planId)}`;
43818
+ }
43795
43819
  var InviteTokenSchema = external_exports.object({
43796
43820
  id: external_exports.string(),
43797
43821
  tokenHash: external_exports.string(),
@@ -45749,7 +45773,7 @@ async function handleUpdatedPlanReview(sessionId, planId, planContent, _originMe
45749
45773
  }
45750
45774
  const baseUrl = webConfig.SHIPYARD_WEB_URL;
45751
45775
  logger.info(
45752
- { planId, url: `${baseUrl}/plan/${planId}` },
45776
+ { planId, url: createPlanWebUrl(baseUrl, planId) },
45753
45777
  "Content synced, browser already open. Waiting for server approval..."
45754
45778
  );
45755
45779
  const decision = await waitForReviewDecision(planId, "");
@@ -45906,7 +45930,7 @@ Reviewer comment: ${decision.reviewComment}` : "";
45906
45930
  allow: false,
45907
45931
  message: `Plan is pending review.
45908
45932
 
45909
- Open: ${baseUrl}/plan/${planId}`,
45933
+ Open: ${createPlanWebUrl(baseUrl, planId)}`,
45910
45934
  planId
45911
45935
  };
45912
45936
  case "draft":
@@ -45914,7 +45938,7 @@ Open: ${baseUrl}/plan/${planId}`,
45914
45938
  allow: false,
45915
45939
  message: `Plan is still in draft.
45916
45940
 
45917
- Submit for review at: ${baseUrl}/plan/${planId}`,
45941
+ Submit for review at: ${createPlanWebUrl(baseUrl, planId)}`,
45918
45942
  planId
45919
45943
  };
45920
45944
  case "in_progress":
@@ -3,7 +3,7 @@ import {
3
3
  YDOC_KEYS,
4
4
  createInputRequest,
5
5
  logPlanEvent
6
- } from "./chunk-76JWRTPI.js";
6
+ } from "./chunk-UFE5KX7E.js";
7
7
  import {
8
8
  logger
9
9
  } from "./chunk-GSGLHRWX.js";
@@ -152,6 +152,34 @@ function getUsernameFromGitConfig() {
152
152
  return null;
153
153
  }
154
154
  }
155
+ async function getVerifiedGitHubUsername() {
156
+ if (githubConfig.GITHUB_USERNAME) {
157
+ logger.info({ username: githubConfig.GITHUB_USERNAME }, "Using GITHUB_USERNAME from env");
158
+ return githubConfig.GITHUB_USERNAME;
159
+ }
160
+ if (githubConfig.GITHUB_TOKEN) {
161
+ try {
162
+ const username = await getUsernameFromToken(githubConfig.GITHUB_TOKEN);
163
+ if (username) {
164
+ logger.info({ username }, "Got verified username from GITHUB_TOKEN");
165
+ return username;
166
+ }
167
+ } catch (error) {
168
+ logger.warn({ error }, "Failed to get username from GITHUB_TOKEN");
169
+ }
170
+ }
171
+ try {
172
+ const username = getUsernameFromCLI();
173
+ if (username) {
174
+ logger.info({ username }, "Got verified username from gh CLI");
175
+ return username;
176
+ }
177
+ } catch (error) {
178
+ logger.debug({ error }, "Failed to get username from gh CLI");
179
+ }
180
+ logger.warn("No verified GitHub authentication available");
181
+ return null;
182
+ }
155
183
  function getGitBranch() {
156
184
  try {
157
185
  return execSync2("git branch --show-current", {
@@ -176,5 +204,6 @@ export {
176
204
  githubConfig,
177
205
  getRepositoryFullName,
178
206
  getGitHubUsername,
207
+ getVerifiedGitHubUsername,
179
208
  getEnvironmentContext
180
209
  };
@@ -552,7 +552,8 @@ var PlanEventTypes = [
552
552
  "input_request_created",
553
553
  "input_request_answered",
554
554
  "input_request_declined",
555
- "agent_activity"
555
+ "agent_activity",
556
+ "session_token_regenerated"
556
557
  ];
557
558
  var AgentActivityTypes = [
558
559
  "help_request",
@@ -701,7 +702,8 @@ var PlanEventSchema = z.discriminatedUnion("type", [
701
702
  PlanEventBaseSchema.extend({
702
703
  type: z.literal("agent_activity"),
703
704
  data: AgentActivityDataSchema
704
- })
705
+ }),
706
+ PlanEventBaseSchema.extend({ type: z.literal("session_token_regenerated") })
705
707
  ]);
706
708
  function isInboxWorthy(event, username, ownerId) {
707
709
  if (!event.inboxWorthy) return false;
@@ -863,7 +865,7 @@ function createHandedOffConversationVersion(params) {
863
865
  return ConversationVersionSchema.parse(version);
864
866
  }
865
867
 
866
- // ../../packages/schema/dist/yjs-helpers-BBG4tC2R.mjs
868
+ // ../../packages/schema/dist/yjs-helpers-A0hIPiRs.mjs
867
869
  import { z as z2 } from "zod";
868
870
  import { nanoid as nanoid2 } from "nanoid";
869
871
  import * as Y from "yjs";
@@ -1753,6 +1755,27 @@ function declineInputRequest(ydoc, requestId) {
1753
1755
  });
1754
1756
  return { success: true };
1755
1757
  }
1758
+ function atomicRegenerateTokenIfOwner(ydoc, expectedOwnerId, newTokenHash, actor) {
1759
+ let result = {
1760
+ success: false,
1761
+ actualOwner: void 0
1762
+ };
1763
+ ydoc.transact(() => {
1764
+ const map = ydoc.getMap(YDOC_KEYS.METADATA);
1765
+ const currentOwner = map.get("ownerId");
1766
+ if (currentOwner !== expectedOwnerId) {
1767
+ result = {
1768
+ success: false,
1769
+ actualOwner: currentOwner
1770
+ };
1771
+ return;
1772
+ }
1773
+ map.set("sessionTokenHash", newTokenHash);
1774
+ map.set("updatedAt", Date.now());
1775
+ result = { success: true };
1776
+ }, actor ? { actor } : void 0);
1777
+ return result;
1778
+ }
1756
1779
 
1757
1780
  // ../../packages/schema/dist/url-encoding.mjs
1758
1781
  var import_lz_string = __toESM(require_lz_string(), 1);
@@ -2251,6 +2274,29 @@ function asWebRTCPeerId(id) {
2251
2274
  function asGitHubUsername(username) {
2252
2275
  return username;
2253
2276
  }
2277
+ var ROUTES = {
2278
+ REGISTRY_LIST: "/registry",
2279
+ REGISTRY_REGISTER: "/register",
2280
+ REGISTRY_UNREGISTER: "/unregister",
2281
+ PLAN_STATUS: (planId) => `/api/plan/${planId}/status`,
2282
+ PLAN_HAS_CONNECTIONS: (planId) => `/api/plan/${planId}/has-connections`,
2283
+ PLAN_TRANSCRIPT: (planId) => `/api/plan/${planId}/transcript`,
2284
+ PLAN_SUBSCRIBE: (planId) => `/api/plan/${planId}/subscribe`,
2285
+ PLAN_CHANGES: (planId) => `/api/plan/${planId}/changes`,
2286
+ PLAN_UNSUBSCRIBE: (planId) => `/api/plan/${planId}/unsubscribe`,
2287
+ PLAN_PR_DIFF: (planId, prNumber) => `/api/plans/${planId}/pr-diff/${prNumber}`,
2288
+ PLAN_PR_FILES: (planId, prNumber) => `/api/plans/${planId}/pr-files/${prNumber}`,
2289
+ HOOK_SESSION: "/api/hook/session",
2290
+ HOOK_CONTENT: (planId) => `/api/hook/plan/${planId}/content`,
2291
+ HOOK_REVIEW: (planId) => `/api/hook/plan/${planId}/review`,
2292
+ HOOK_SESSION_TOKEN: (planId) => `/api/hook/plan/${planId}/session-token`,
2293
+ HOOK_PRESENCE: (planId) => `/api/hook/plan/${planId}/presence`,
2294
+ CONVERSATION_IMPORT: "/api/conversation/import",
2295
+ WEB_TASK: (planId) => `/task/${planId}`
2296
+ };
2297
+ function createPlanWebUrl(baseUrl, planId) {
2298
+ return `${baseUrl.replace(/\/$/, "")}${ROUTES.WEB_TASK(planId)}`;
2299
+ }
2254
2300
  var InviteTokenSchema = z3.object({
2255
2301
  id: z3.string(),
2256
2302
  tokenHash: z3.string(),
@@ -2284,7 +2330,7 @@ function parseInviteFromUrl(url) {
2284
2330
  }
2285
2331
  function buildInviteUrl(baseUrl, planId, tokenId, tokenValue) {
2286
2332
  const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
2287
- const url = new URL(`${normalizedBase}/task/${planId}`);
2333
+ const url = new URL(`${normalizedBase}${ROUTES.WEB_TASK(planId)}`);
2288
2334
  url.searchParams.set("invite", `${tokenId}:${tokenValue}`);
2289
2335
  return url.toString();
2290
2336
  }
@@ -2565,25 +2611,6 @@ function getAllEventViewedByForPlan(ydoc, planId) {
2565
2611
  }
2566
2612
  return result;
2567
2613
  }
2568
- var ROUTES = {
2569
- REGISTRY_LIST: "/registry",
2570
- REGISTRY_REGISTER: "/register",
2571
- REGISTRY_UNREGISTER: "/unregister",
2572
- PLAN_STATUS: (planId) => `/api/plan/${planId}/status`,
2573
- PLAN_HAS_CONNECTIONS: (planId) => `/api/plan/${planId}/has-connections`,
2574
- PLAN_TRANSCRIPT: (planId) => `/api/plan/${planId}/transcript`,
2575
- PLAN_SUBSCRIBE: (planId) => `/api/plan/${planId}/subscribe`,
2576
- PLAN_CHANGES: (planId) => `/api/plan/${planId}/changes`,
2577
- PLAN_UNSUBSCRIBE: (planId) => `/api/plan/${planId}/unsubscribe`,
2578
- PLAN_PR_DIFF: (planId, prNumber) => `/api/plans/${planId}/pr-diff/${prNumber}`,
2579
- PLAN_PR_FILES: (planId, prNumber) => `/api/plans/${planId}/pr-files/${prNumber}`,
2580
- HOOK_SESSION: "/api/hook/session",
2581
- HOOK_CONTENT: (planId) => `/api/hook/plan/${planId}/content`,
2582
- HOOK_REVIEW: (planId) => `/api/hook/plan/${planId}/review`,
2583
- HOOK_SESSION_TOKEN: (planId) => `/api/hook/plan/${planId}/session-token`,
2584
- HOOK_PRESENCE: (planId) => `/api/hook/plan/${planId}/presence`,
2585
- CONVERSATION_IMPORT: "/api/conversation/import"
2586
- };
2587
2614
  function formatThreadsForLLM(threads, options = {}) {
2588
2615
  const { includeResolved = false, selectedTextMaxLength = 100, resolveUser } = options;
2589
2616
  const unresolvedThreads = threads.filter((t$1) => !t$1.resolved);
@@ -2896,6 +2923,7 @@ export {
2896
2923
  answerInputRequest,
2897
2924
  cancelInputRequest,
2898
2925
  declineInputRequest,
2926
+ atomicRegenerateTokenIfOwner,
2899
2927
  isUrlEncodedPlanV1,
2900
2928
  isUrlEncodedPlanV2,
2901
2929
  encodePlan,
@@ -2923,6 +2951,8 @@ export {
2923
2951
  asAwarenessClientId,
2924
2952
  asWebRTCPeerId,
2925
2953
  asGitHubUsername,
2954
+ ROUTES,
2955
+ createPlanWebUrl,
2926
2956
  InviteTokenSchema,
2927
2957
  InviteRedemptionSchema,
2928
2958
  parseInviteFromUrl,
@@ -2963,7 +2993,6 @@ export {
2963
2993
  clearEventViewedBy,
2964
2994
  isEventUnread,
2965
2995
  getAllEventViewedByForPlan,
2966
- ROUTES,
2967
2996
  formatThreadsForLLM,
2968
2997
  PlanIdSchema,
2969
2998
  PlanStatusResponseSchema,
@@ -92,6 +92,7 @@ import {
92
92
  asWebRTCPeerId,
93
93
  assertNever,
94
94
  assertNeverP2PMessage,
95
+ atomicRegenerateTokenIfOwner,
95
96
  buildInviteUrl,
96
97
  cancelInputRequest,
97
98
  claudeCodeToA2A,
@@ -108,6 +109,7 @@ import {
108
109
  createPlanSnapshot,
109
110
  createPlanUrl,
110
111
  createPlanUrlWithHistory,
112
+ createPlanWebUrl,
111
113
  createUserResolver,
112
114
  declineInputRequest,
113
115
  decodeChunkMessage,
@@ -204,7 +206,7 @@ import {
204
206
  updateLinkedPRStatus,
205
207
  updatePlanIndexViewedBy,
206
208
  validateA2AMessages
207
- } from "./chunk-76JWRTPI.js";
209
+ } from "./chunk-UFE5KX7E.js";
208
210
  import "./chunk-JSBRDJBE.js";
209
211
  export {
210
212
  A2ADataPartSchema,
@@ -300,6 +302,7 @@ export {
300
302
  asWebRTCPeerId,
301
303
  assertNever,
302
304
  assertNeverP2PMessage,
305
+ atomicRegenerateTokenIfOwner,
303
306
  buildInviteUrl,
304
307
  cancelInputRequest,
305
308
  claudeCodeToA2A,
@@ -316,6 +319,7 @@ export {
316
319
  createPlanSnapshot,
317
320
  createPlanUrl,
318
321
  createPlanUrlWithHistory,
322
+ createPlanWebUrl,
319
323
  createUserResolver,
320
324
  declineInputRequest,
321
325
  decodeChunkMessage,
@@ -3,8 +3,9 @@ import {
3
3
  getEnvironmentContext,
4
4
  getGitHubUsername,
5
5
  getRepositoryFullName,
6
+ getVerifiedGitHubUsername,
6
7
  githubConfig
7
- } from "./chunk-E5DWX2WU.js";
8
+ } from "./chunk-OLFHVNPI.js";
8
9
  import {
9
10
  assertNever,
10
11
  getSessionIdByPlanId,
@@ -19,7 +20,7 @@ import {
19
20
  } from "./chunk-EBNL5ZX7.js";
20
21
  import {
21
22
  InputRequestManager
22
- } from "./chunk-BWP37ADP.js";
23
+ } from "./chunk-B4TQH7Q3.js";
23
24
  import {
24
25
  ArtifactSchema,
25
26
  DeliverableSchema,
@@ -40,10 +41,12 @@ import {
40
41
  addPRReviewComment,
41
42
  addSnapshot,
42
43
  appRouter,
44
+ atomicRegenerateTokenIfOwner,
43
45
  createInitialConversationVersion,
44
46
  createLinkedPR,
45
47
  createPlanSnapshot,
46
48
  createPlanUrlWithHistory,
49
+ createPlanWebUrl,
47
50
  createUserResolver,
48
51
  extractDeliverables,
49
52
  extractMentions,
@@ -69,7 +72,7 @@ import {
69
72
  setPlanMetadata,
70
73
  touchPlanIndexEntry,
71
74
  transitionPlanStatus
72
- } from "./chunk-76JWRTPI.js";
75
+ } from "./chunk-UFE5KX7E.js";
73
76
  import {
74
77
  loadEnv,
75
78
  logger
@@ -590,8 +593,7 @@ function extractTitleFromBlocks(blocks) {
590
593
  async function createSessionHandler(input, ctx) {
591
594
  const existingSession = getSessionState(input.sessionId);
592
595
  if (existingSession) {
593
- const webUrl2 = webConfig.SHIPYARD_WEB_URL;
594
- const url2 = `${webUrl2}/plan/${existingSession.planId}`;
596
+ const url2 = createPlanWebUrl(webConfig.SHIPYARD_WEB_URL, existingSession.planId);
595
597
  ctx.logger.info(
596
598
  { planId: existingSession.planId, sessionId: input.sessionId },
597
599
  "Returning existing session (idempotent)"
@@ -656,8 +658,7 @@ async function createSessionHandler(input, ctx) {
656
658
  ownerId,
657
659
  deleted: false
658
660
  });
659
- const webUrl = webConfig.SHIPYARD_WEB_URL;
660
- const url = `${webUrl}/plan/${planId}`;
661
+ const url = createPlanWebUrl(webConfig.SHIPYARD_WEB_URL, planId);
661
662
  ctx.logger.info({ url }, "Plan URL generated");
662
663
  setSessionState(input.sessionId, {
663
664
  lifecycle: "created",
@@ -836,8 +837,7 @@ async function setSessionTokenHandler(planId, sessionTokenHash, ctx) {
836
837
  setPlanMetadata(ydoc, {
837
838
  sessionTokenHash
838
839
  });
839
- const webUrl = webConfig.SHIPYARD_WEB_URL;
840
- const url = `${webUrl}/plan/${planId}`;
840
+ const url = createPlanWebUrl(webConfig.SHIPYARD_WEB_URL, planId);
841
841
  const session = getSessionStateByPlanId(planId);
842
842
  const sessionId = getSessionIdByPlanId(planId);
843
843
  if (session && sessionId) {
@@ -1017,7 +1017,7 @@ async function waitForApprovalHandler(planId, _reviewRequestIdParam, ctx) {
1017
1017
  setSessionState(sessionId, {
1018
1018
  lifecycle: "approved_awaiting_token",
1019
1019
  ...baseState,
1020
- url: `${webUrl}/plan/${baseState.planId}`,
1020
+ url: createPlanWebUrl(webUrl, baseState.planId),
1021
1021
  approvedAt: extraData.approvedAt,
1022
1022
  deliverables: extraData.deliverables,
1023
1023
  reviewComment,
@@ -1036,7 +1036,7 @@ async function waitForApprovalHandler(planId, _reviewRequestIdParam, ctx) {
1036
1036
  ...baseState,
1037
1037
  contentHash: syncedFields?.contentHash ?? "",
1038
1038
  sessionToken: syncedFields?.sessionToken ?? "",
1039
- url: syncedFields?.url ?? `${webUrl}/plan/${baseState.planId}`,
1039
+ url: syncedFields?.url ?? createPlanWebUrl(webUrl, baseState.planId),
1040
1040
  deliverables,
1041
1041
  reviewComment: reviewComment || "",
1042
1042
  reviewedBy,
@@ -1229,8 +1229,7 @@ async function getDeliverableContextHandler(planId, sessionToken, ctx) {
1229
1229
  });
1230
1230
  }
1231
1231
  const deliverables = getDeliverables(ydoc);
1232
- const webUrl = webConfig.SHIPYARD_WEB_URL;
1233
- const url = `${webUrl}/plan/${planId}`;
1232
+ const url = createPlanWebUrl(webConfig.SHIPYARD_WEB_URL, planId);
1234
1233
  let deliverablesSection = "";
1235
1234
  if (deliverables.length > 0) {
1236
1235
  deliverablesSection = `
@@ -2472,7 +2471,7 @@ import * as os from "os";
2472
2471
  import * as path from "path";
2473
2472
  import * as vm from "vm";
2474
2473
  import ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
2475
- import { z as z12 } from "zod";
2474
+ import { z as z13 } from "zod";
2476
2475
 
2477
2476
  // src/tools/add-artifact.ts
2478
2477
  import { execSync } from "child_process";
@@ -2538,6 +2537,7 @@ var TOOL_NAMES = {
2538
2537
  EXECUTE_CODE: "execute_code",
2539
2538
  LINK_PR: "link_pr",
2540
2539
  READ_PLAN: "read_plan",
2540
+ REGENERATE_SESSION_TOKEN: "regenerate_session_token",
2541
2541
  REQUEST_USER_INPUT: "request_user_input",
2542
2542
  SETUP_REVIEW_NOTIFICATION: "setup_review_notification",
2543
2543
  UPDATE_BLOCK_CONTENT: "update_block_content",
@@ -3627,7 +3627,7 @@ Bad deliverables (not provable):
3627
3627
  deleted: false
3628
3628
  });
3629
3629
  logger.info({ planId }, "Plan index updated");
3630
- const url = `${webConfig.SHIPYARD_WEB_URL}/plan/${planId}`;
3630
+ const url = createPlanWebUrl(webConfig.SHIPYARD_WEB_URL, planId);
3631
3631
  await openPlanInBrowser(planId, url);
3632
3632
  const repoInfo = repo ? `Repo: ${repo}${!input.repo ? " (auto-detected)" : ""}` : "Repo: Not set (provide repo and prNumber for artifact uploads)";
3633
3633
  return {
@@ -4136,11 +4136,146 @@ OUTPUT INCLUDES:
4136
4136
  }
4137
4137
  };
4138
4138
 
4139
- // src/tools/setup-review-notification.ts
4139
+ // src/tools/regenerate-session-token.ts
4140
4140
  import { z as z9 } from "zod";
4141
- var SetupReviewNotificationInput = z9.object({
4142
- planId: z9.string().describe("Plan ID to monitor"),
4143
- pollIntervalSeconds: z9.number().optional().default(30).describe("Polling interval in seconds (default: 30)")
4141
+ var RegenerateSessionTokenInput = z9.object({
4142
+ planId: z9.string().describe("The plan ID to regenerate token for")
4143
+ });
4144
+ var regenerateSessionTokenTool = {
4145
+ definition: {
4146
+ name: TOOL_NAMES.REGENERATE_SESSION_TOKEN,
4147
+ description: `Regenerate the session token for a plan.
4148
+
4149
+ USE WHEN:
4150
+ - Your Claude Code session ended and you lost the original token
4151
+ - You need to resume work on a plan you own
4152
+ - The old token may have been compromised
4153
+
4154
+ REQUIREMENTS:
4155
+ - You must be the plan owner (verified via GitHub identity)
4156
+ - The plan must exist and have an ownerId set
4157
+
4158
+ RETURNS:
4159
+ - New session token that can be used for add_artifact, read_plan, etc.
4160
+
4161
+ SECURITY:
4162
+ - Only the plan owner can regenerate tokens
4163
+ - Old token is immediately invalidated
4164
+ - New token is returned only once - store it securely`,
4165
+ inputSchema: {
4166
+ type: "object",
4167
+ properties: {
4168
+ planId: { type: "string", description: "The plan ID to regenerate token for" }
4169
+ },
4170
+ required: ["planId"]
4171
+ }
4172
+ },
4173
+ handler: async (args) => {
4174
+ const { planId } = RegenerateSessionTokenInput.parse(args);
4175
+ logger.info({ planId }, "Attempting to regenerate session token");
4176
+ const currentUser = await getVerifiedGitHubUsername();
4177
+ if (!currentUser) {
4178
+ return {
4179
+ content: [
4180
+ {
4181
+ type: "text",
4182
+ text: `Token regeneration requires verified GitHub authentication.
4183
+
4184
+ Please configure ONE of:
4185
+ 1. GITHUB_USERNAME environment variable
4186
+ 2. GITHUB_TOKEN environment variable (will verify via API)
4187
+ 3. Run: gh auth login
4188
+
4189
+ Note: git config user.name is NOT accepted for security-critical operations.`
4190
+ }
4191
+ ],
4192
+ isError: true
4193
+ };
4194
+ }
4195
+ const doc = await getOrCreateDoc3(planId);
4196
+ const metadata = getPlanMetadata(doc);
4197
+ if (!metadata) {
4198
+ return {
4199
+ content: [{ type: "text", text: `Plan "${planId}" not found.` }],
4200
+ isError: true
4201
+ };
4202
+ }
4203
+ if (!metadata.ownerId) {
4204
+ return {
4205
+ content: [
4206
+ {
4207
+ type: "text",
4208
+ text: `Plan "${planId}" has no owner set. Cannot regenerate token for ownerless plans.`
4209
+ }
4210
+ ],
4211
+ isError: true
4212
+ };
4213
+ }
4214
+ const newToken = generateSessionToken();
4215
+ const newTokenHash = hashSessionToken(newToken);
4216
+ const updateResult = atomicRegenerateTokenIfOwner(
4217
+ doc,
4218
+ metadata.ownerId,
4219
+ newTokenHash,
4220
+ currentUser
4221
+ );
4222
+ if (!updateResult.success) {
4223
+ const actualOwner = updateResult.actualOwner;
4224
+ if (actualOwner !== currentUser) {
4225
+ logger.warn(
4226
+ { planId, expectedOwner: metadata.ownerId, actualOwner, currentUser },
4227
+ "Token regeneration denied - ownership changed during operation"
4228
+ );
4229
+ return {
4230
+ content: [
4231
+ {
4232
+ type: "text",
4233
+ text: `Access denied. You do not have permission to regenerate the session token for plan "${planId}".`
4234
+ }
4235
+ ],
4236
+ isError: true
4237
+ };
4238
+ }
4239
+ logger.error(
4240
+ { planId, expectedOwner: metadata.ownerId, actualOwner, currentUser },
4241
+ "Unexpected failure in atomic token regeneration"
4242
+ );
4243
+ return {
4244
+ content: [
4245
+ {
4246
+ type: "text",
4247
+ text: "Token regeneration failed due to an unexpected error. Please try again."
4248
+ }
4249
+ ],
4250
+ isError: true
4251
+ };
4252
+ }
4253
+ logPlanEvent(doc, "session_token_regenerated", currentUser);
4254
+ logger.info({ planId, ownerId: metadata.ownerId }, "Session token regenerated successfully");
4255
+ return {
4256
+ content: [
4257
+ {
4258
+ type: "text",
4259
+ text: `Session token regenerated successfully!
4260
+
4261
+ Plan: ${metadata.title}
4262
+ Plan ID: ${planId}
4263
+
4264
+ New Session Token: ${newToken}
4265
+
4266
+ IMPORTANT: Store this token securely. The old token has been invalidated.
4267
+ Use this token for add_artifact, read_plan, link_pr, and other plan operations.`
4268
+ }
4269
+ ]
4270
+ };
4271
+ }
4272
+ };
4273
+
4274
+ // src/tools/setup-review-notification.ts
4275
+ import { z as z10 } from "zod";
4276
+ var SetupReviewNotificationInput = z10.object({
4277
+ planId: z10.string().describe("Plan ID to monitor"),
4278
+ pollIntervalSeconds: z10.number().optional().default(30).describe("Polling interval in seconds (default: 30)")
4144
4279
  });
4145
4280
  var setupReviewNotificationTool = {
4146
4281
  definition: {
@@ -4258,31 +4393,31 @@ The script:
4258
4393
 
4259
4394
  // src/tools/update-block-content.ts
4260
4395
  import { ServerBlockNoteEditor as ServerBlockNoteEditor6 } from "@blocknote/server-util";
4261
- import { z as z10 } from "zod";
4262
- var BlockOperationSchema = z10.discriminatedUnion("type", [
4263
- z10.object({
4264
- type: z10.literal("update"),
4265
- blockId: z10.string().describe("The block ID to update (from read_plan output)"),
4266
- content: z10.string().describe("New markdown content for this block")
4396
+ import { z as z11 } from "zod";
4397
+ var BlockOperationSchema = z11.discriminatedUnion("type", [
4398
+ z11.object({
4399
+ type: z11.literal("update"),
4400
+ blockId: z11.string().describe("The block ID to update (from read_plan output)"),
4401
+ content: z11.string().describe("New markdown content for this block")
4267
4402
  }),
4268
- z10.object({
4269
- type: z10.literal("insert"),
4270
- afterBlockId: z10.string().nullable().describe("Insert after this block ID (null = insert at beginning)"),
4271
- content: z10.string().describe("Markdown content to insert as new block(s)")
4403
+ z11.object({
4404
+ type: z11.literal("insert"),
4405
+ afterBlockId: z11.string().nullable().describe("Insert after this block ID (null = insert at beginning)"),
4406
+ content: z11.string().describe("Markdown content to insert as new block(s)")
4272
4407
  }),
4273
- z10.object({
4274
- type: z10.literal("delete"),
4275
- blockId: z10.string().describe("The block ID to delete")
4408
+ z11.object({
4409
+ type: z11.literal("delete"),
4410
+ blockId: z11.string().describe("The block ID to delete")
4276
4411
  }),
4277
- z10.object({
4278
- type: z10.literal("replace_all"),
4279
- content: z10.string().describe("Complete markdown content to replace the entire plan")
4412
+ z11.object({
4413
+ type: z11.literal("replace_all"),
4414
+ content: z11.string().describe("Complete markdown content to replace the entire plan")
4280
4415
  })
4281
4416
  ]);
4282
- var UpdateBlockContentInput = z10.object({
4283
- planId: z10.string().describe("The plan ID to modify"),
4284
- sessionToken: z10.string().describe("Session token from create_plan"),
4285
- operations: z10.array(BlockOperationSchema).min(1).describe("Array of operations to perform atomically")
4417
+ var UpdateBlockContentInput = z11.object({
4418
+ planId: z11.string().describe("The plan ID to modify"),
4419
+ sessionToken: z11.string().describe("Session token from create_plan"),
4420
+ operations: z11.array(BlockOperationSchema).min(1).describe("Array of operations to perform atomically")
4286
4421
  });
4287
4422
  var updateBlockContentTool = {
4288
4423
  definition: {
@@ -4544,7 +4679,7 @@ async function applyOperation(blocks, operation, editor) {
4544
4679
  // src/tools/update-plan.ts
4545
4680
  import { ServerBlockNoteEditor as ServerBlockNoteEditor7 } from "@blocknote/server-util";
4546
4681
  import { nanoid as nanoid7 } from "nanoid";
4547
- import { z as z11 } from "zod";
4682
+ import { z as z12 } from "zod";
4548
4683
  function buildStatusTransition(targetStatus, actorName) {
4549
4684
  const now = Date.now();
4550
4685
  switch (targetStatus) {
@@ -4577,12 +4712,12 @@ function buildStatusTransition(targetStatus, actorName) {
4577
4712
  return null;
4578
4713
  }
4579
4714
  }
4580
- var UpdatePlanInput = z11.object({
4581
- planId: z11.string().describe("The plan ID to update"),
4582
- sessionToken: z11.string().describe("Session token from create_plan"),
4583
- title: z11.string().optional().describe("New title"),
4584
- status: z11.enum(["draft", "pending_review", "changes_requested", "in_progress", "completed"]).optional().describe("New status"),
4585
- tags: z11.array(z11.string()).optional().describe("Updated tags (replaces existing tags)")
4715
+ var UpdatePlanInput = z12.object({
4716
+ planId: z12.string().describe("The plan ID to update"),
4717
+ sessionToken: z12.string().describe("Session token from create_plan"),
4718
+ title: z12.string().optional().describe("New title"),
4719
+ status: z12.enum(["draft", "pending_review", "changes_requested", "in_progress", "completed"]).optional().describe("New status"),
4720
+ tags: z12.array(z12.string()).optional().describe("Updated tags (replaces existing tags)")
4586
4721
  });
4587
4722
  var updatePlanTool = {
4588
4723
  definition: {
@@ -5035,6 +5170,46 @@ await resolveActivityRequest({
5035
5170
 
5036
5171
  ---
5037
5172
 
5173
+ ### regenerateSessionToken(planId): Promise<{ sessionToken, planId }>
5174
+ Regenerate the session token for a plan you own.
5175
+
5176
+ USE WHEN:
5177
+ - Your Claude Code session ended and you lost the original token
5178
+ - You need to resume work on a plan from a previous session
5179
+ - The old token may have been compromised
5180
+
5181
+ REQUIREMENTS:
5182
+ - You must be the plan owner (verified via GitHub identity)
5183
+ - The plan must exist and have an ownerId set
5184
+
5185
+ Returns:
5186
+ - sessionToken: New token for API calls
5187
+ - planId: The plan ID
5188
+
5189
+ SECURITY:
5190
+ - Only the plan owner can regenerate tokens
5191
+ - Old token is immediately invalidated
5192
+ - GitHub identity verification happens on MCP server (via gh auth login)
5193
+
5194
+ Example:
5195
+ \`\`\`typescript
5196
+ // Lost your session token? Regenerate it:
5197
+ const { sessionToken, planId } = await regenerateSessionToken("abc123");
5198
+
5199
+ // Now use the new token for operations
5200
+ await addArtifact({
5201
+ planId,
5202
+ sessionToken,
5203
+ type: 'screenshot',
5204
+ filename: 'screenshot.png',
5205
+ source: 'file',
5206
+ filePath: '/tmp/screenshot.png',
5207
+ deliverableId: 'del_xxx'
5208
+ });
5209
+ \`\`\`
5210
+
5211
+ ---
5212
+
5038
5213
  ## Common Pattern
5039
5214
 
5040
5215
  \`\`\`typescript
@@ -5072,8 +5247,8 @@ const result = await addArtifact({
5072
5247
  return { planId: plan.planId, snapshotUrl: result.snapshotUrl };
5073
5248
  \`\`\`
5074
5249
  `;
5075
- var ExecuteCodeInput = z12.object({
5076
- code: z12.string().describe("TypeScript code to execute")
5250
+ var ExecuteCodeInput = z13.object({
5251
+ code: z13.string().describe("TypeScript code to execute")
5077
5252
  });
5078
5253
  var scriptTracker = [];
5079
5254
  async function createPlan(opts) {
@@ -5208,7 +5383,7 @@ async function setupReviewNotification(planId, pollIntervalSeconds) {
5208
5383
  return { script, fullResponse: text };
5209
5384
  }
5210
5385
  async function requestUserInput(opts) {
5211
- const { InputRequestManager: InputRequestManager2 } = await import("./input-request-manager-73GSTOIB.js");
5386
+ const { InputRequestManager: InputRequestManager2 } = await import("./input-request-manager-IMVZDMUQ.js");
5212
5387
  const ydoc = await getOrCreateDoc3(PLAN_INDEX_DOC_NAME);
5213
5388
  const manager = new InputRequestManager2();
5214
5389
  const params = opts.type === "choice" ? {
@@ -5252,8 +5427,8 @@ async function requestUserInput(opts) {
5252
5427
  };
5253
5428
  }
5254
5429
  async function postActivityUpdate(opts) {
5255
- const { logPlanEvent: logPlanEvent2 } = await import("./dist-ORKL4P3L.js");
5256
- const { getGitHubUsername: getGitHubUsername2 } = await import("./server-identity-6PHKR2FY.js");
5430
+ const { logPlanEvent: logPlanEvent2 } = await import("./dist-E4CPV3SO.js");
5431
+ const { getGitHubUsername: getGitHubUsername2 } = await import("./server-identity-LSZ4CZRK.js");
5257
5432
  const { nanoid: nanoid8 } = await import("nanoid");
5258
5433
  const doc = await getOrCreateDoc3(opts.planId);
5259
5434
  const actorName = await getGitHubUsername2();
@@ -5275,8 +5450,8 @@ async function postActivityUpdate(opts) {
5275
5450
  return { success: true, eventId, requestId };
5276
5451
  }
5277
5452
  async function resolveActivityRequest(opts) {
5278
- const { logPlanEvent: logPlanEvent2, getPlanEvents } = await import("./dist-ORKL4P3L.js");
5279
- const { getGitHubUsername: getGitHubUsername2 } = await import("./server-identity-6PHKR2FY.js");
5453
+ const { logPlanEvent: logPlanEvent2, getPlanEvents } = await import("./dist-E4CPV3SO.js");
5454
+ const { getGitHubUsername: getGitHubUsername2 } = await import("./server-identity-LSZ4CZRK.js");
5280
5455
  const doc = await getOrCreateDoc3(opts.planId);
5281
5456
  const actorName = await getGitHubUsername2();
5282
5457
  const events = getPlanEvents(doc);
@@ -5301,6 +5476,18 @@ async function resolveActivityRequest(opts) {
5301
5476
  });
5302
5477
  return { success: true };
5303
5478
  }
5479
+ async function regenerateSessionToken(planId) {
5480
+ const result = await regenerateSessionTokenTool.handler({ planId });
5481
+ const text = result.content[0]?.text || "";
5482
+ if (result.isError) {
5483
+ throw new Error(text);
5484
+ }
5485
+ const tokenMatch = text.match(/New Session Token: (\S+)/);
5486
+ return {
5487
+ sessionToken: tokenMatch?.[1] || "",
5488
+ planId
5489
+ };
5490
+ }
5304
5491
  var executeCodeTool = {
5305
5492
  definition: {
5306
5493
  name: TOOL_NAMES.EXECUTE_CODE,
@@ -5365,6 +5552,7 @@ var executeCodeTool = {
5365
5552
  requestUserInput,
5366
5553
  postActivityUpdate,
5367
5554
  resolveActivityRequest,
5555
+ regenerateSessionToken,
5368
5556
  // Video encoding helper (uses bundled FFmpeg)
5369
5557
  encodeVideo,
5370
5558
  // Node.js modules for advanced workflows (file ops, process spawning)
@@ -5423,15 +5611,15 @@ The script will exit when the human approves or requests changes.`
5423
5611
  };
5424
5612
 
5425
5613
  // src/tools/request-user-input.ts
5426
- import { z as z13 } from "zod";
5427
- var RequestUserInputInput = z13.object({
5428
- message: z13.string().describe("The question to ask the user"),
5429
- type: z13.enum(["text", "choice", "confirm", "multiline"]).describe("Type of input to request"),
5430
- options: z13.array(z13.string()).optional().describe("For 'choice' type - available options (required for choice)"),
5431
- multiSelect: z13.boolean().optional().describe("For 'choice' type - allow selecting multiple options"),
5432
- defaultValue: z13.string().optional().describe("Pre-filled value for text/multiline inputs"),
5433
- timeout: z13.number().optional().describe("Timeout in seconds (default: 1800, min: 10, max: 14400)"),
5434
- planId: z13.string().optional().describe("Optional metadata to link request to plan (for activity log filtering)")
5614
+ import { z as z14 } from "zod";
5615
+ var RequestUserInputInput = z14.object({
5616
+ message: z14.string().describe("The question to ask the user"),
5617
+ type: z14.enum(["text", "choice", "confirm", "multiline"]).describe("Type of input to request"),
5618
+ options: z14.array(z14.string()).optional().describe("For 'choice' type - available options (required for choice)"),
5619
+ multiSelect: z14.boolean().optional().describe("For 'choice' type - allow selecting multiple options"),
5620
+ defaultValue: z14.string().optional().describe("Pre-filled value for text/multiline inputs"),
5621
+ timeout: z14.number().optional().describe("Timeout in seconds (default: 1800, min: 10, max: 14400)"),
5622
+ planId: z14.string().optional().describe("Optional metadata to link request to plan (for activity log filtering)")
5435
5623
  });
5436
5624
  var requestUserInputTool = {
5437
5625
  definition: {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  InputRequestManager
3
- } from "./chunk-BWP37ADP.js";
4
- import "./chunk-76JWRTPI.js";
3
+ } from "./chunk-B4TQH7Q3.js";
4
+ import "./chunk-UFE5KX7E.js";
5
5
  import "./chunk-GSGLHRWX.js";
6
6
  import "./chunk-JSBRDJBE.js";
7
7
  export {
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  getEnvironmentContext,
3
3
  getGitHubUsername,
4
- getRepositoryFullName
5
- } from "./chunk-E5DWX2WU.js";
4
+ getRepositoryFullName,
5
+ getVerifiedGitHubUsername
6
+ } from "./chunk-OLFHVNPI.js";
6
7
  import "./chunk-GSGLHRWX.js";
7
8
  import "./chunk-JSBRDJBE.js";
8
9
  export {
9
10
  getEnvironmentContext,
10
11
  getGitHubUsername,
11
- getRepositoryFullName
12
+ getRepositoryFullName,
13
+ getVerifiedGitHubUsername
12
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schoolai/shipyard-mcp",
3
- "version": "0.2.2-next.483",
3
+ "version": "0.2.2-next.487",
4
4
  "description": "Shipyard MCP server and CLI tools for distributed planning with CRDTs",
5
5
  "type": "module",
6
6
  "bin": {