teamcopilot 0.3.6 → 0.4.1

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 (65) hide show
  1. package/dist/chat/index.js +104 -81
  2. package/dist/cronjob/index.js +2 -0
  3. package/dist/cronjobs/index.js +822 -0
  4. package/dist/cronjobs/scheduler.js +936 -0
  5. package/dist/frontend/assets/{cssMode-BRVRAYCz.js → cssMode-Cqdl5sUM.js} +1 -1
  6. package/dist/frontend/assets/{freemarker2-B5FvHwsO.js → freemarker2-ykAhuplU.js} +1 -1
  7. package/dist/frontend/assets/{handlebars-DWX2asql.js → handlebars-DX_JwRM8.js} +1 -1
  8. package/dist/frontend/assets/{html-BEBxxD9G.js → html-Bi_zOcbU.js} +1 -1
  9. package/dist/frontend/assets/{htmlMode-B2LbPTwC.js → htmlMode-CkAUoAah.js} +1 -1
  10. package/dist/frontend/assets/index-Ba9bElZm.css +1 -0
  11. package/dist/frontend/assets/{index-D3TE04C5.js → index-Cgozj4fx.js} +245 -242
  12. package/dist/frontend/assets/{javascript-Bh4JwoPV.js → javascript-D3Rjwp97.js} +1 -1
  13. package/dist/frontend/assets/{jsonMode-7j-aplXT.js → jsonMode-K4i6LjP2.js} +1 -1
  14. package/dist/frontend/assets/{liquid-BP4OxkO7.js → liquid-D8F4-sAz.js} +1 -1
  15. package/dist/frontend/assets/{mdx-C1OIcGbY.js → mdx-C2xw8PNz.js} +1 -1
  16. package/dist/frontend/assets/{python-BO8Wy5jz.js → python-CqTGfu2v.js} +1 -1
  17. package/dist/frontend/assets/{razor-BDtqXvAH.js → razor-DFSsPzdZ.js} +1 -1
  18. package/dist/frontend/assets/{tsMode-D22HcCuX.js → tsMode-BkLQEtPb.js} +1 -1
  19. package/dist/frontend/assets/{typescript-CagwEzRw.js → typescript-CE_GQ-M1.js} +1 -1
  20. package/dist/frontend/assets/{xml-fE5sGZ5z.js → xml-CGjMtNcA.js} +1 -1
  21. package/dist/frontend/assets/{yaml-CZMoG4WG.js → yaml-Zju9kuFB.js} +1 -1
  22. package/dist/frontend/index.html +2 -2
  23. package/dist/index.js +3 -0
  24. package/dist/types/cronjob.js +2 -0
  25. package/dist/utils/chat-prompt-context.js +65 -0
  26. package/dist/utils/chat-session.js +12 -0
  27. package/dist/utils/index.js +27 -0
  28. package/dist/utils/workflow-interruption.js +5 -2
  29. package/dist/utils/workflow-run-validation.js +25 -0
  30. package/dist/utils/workspace-sync.js +17 -0
  31. package/dist/workflows/index.js +24 -25
  32. package/dist/workspace_files/.opencode/plugins/apply-patch-session-diff.ts +2 -2
  33. package/dist/workspace_files/.opencode/plugins/askCronjobUser.ts +106 -0
  34. package/dist/workspace_files/.opencode/plugins/manageCronjobTodos.ts +190 -0
  35. package/dist/workspace_files/.opencode/plugins/manageCronjobs.ts +376 -0
  36. package/dist/workspace_files/.opencode/plugins/markCronjobCompleted.ts +107 -0
  37. package/dist/workspace_files/.opencode/plugins/markCronjobFailed.ts +107 -0
  38. package/dist/workspace_files/AGENTS.md +51 -1
  39. package/package.json +1 -1
  40. package/prisma/generated/client/edge.js +50 -3
  41. package/prisma/generated/client/index-browser.js +47 -0
  42. package/prisma/generated/client/index.d.ts +13918 -7530
  43. package/prisma/generated/client/index.js +50 -3
  44. package/prisma/generated/client/package.json +1 -1
  45. package/prisma/generated/client/schema.prisma +72 -1
  46. package/prisma/generated/client/wasm.js +50 -3
  47. package/prisma/migrations/20260508050030_add_cronjobs/migration.sql +78 -0
  48. package/prisma/migrations/20260508093158_add_structured_cronjob_schedules/migration.sql +23 -0
  49. package/prisma/migrations/20260508105129_add_cronjob_targets/migration.sql +50 -0
  50. package/prisma/migrations/20260509044545_flatten_cronjob_schema/migration.sql +88 -0
  51. package/prisma/migrations/20260509052232_simplify_cronjob_schedule_storage/migration.sql +42 -0
  52. package/prisma/migrations/20260509054000_remove_chat_session_source_add_cronjob_run_indexes/migration.sql +28 -0
  53. package/prisma/migrations/20260509061000_cascade_cronjob_run_links/migration.sql +29 -0
  54. package/prisma/migrations/20260513073541_add_cronjob_run_todos/migration.sql +18 -0
  55. package/prisma/migrations/20260513133021_add_cronjob_user_response_wait/migration.sql +29 -0
  56. package/prisma/migrations/20260513135733_add_cronjob_user_handoff_state/migration.sql +30 -0
  57. package/prisma/migrations/20260513142511_drop_awaiting_user_response/migration.sql +35 -0
  58. package/prisma/migrations/20260514032204_simplify_cronjob_run_lifecycle/migration.sql +34 -0
  59. package/prisma/migrations/20260514043000_clear_cronjob_run_history/migration.sql +6 -0
  60. package/prisma/migrations/20260515094618_add_todo_list_version/migration.sql +32 -0
  61. package/prisma/migrations/20260516082714_add_cronjob_monitor_timeout/migration.sql +38 -0
  62. package/prisma/migrations/20260516083452_allow_decimal_cronjob_timeout/migration.sql +37 -0
  63. package/prisma/migrations/20260516084455_add_cronjob_timeout_defaults/migration.sql +31 -0
  64. package/prisma/schema.prisma +71 -1
  65. package/dist/frontend/assets/index-D1Hcz_bo.css +0 -1
@@ -9,9 +9,6 @@ const path_1 = __importDefault(require("path"));
9
9
  const url_1 = require("url");
10
10
  const client_1 = __importDefault(require("../prisma/client"));
11
11
  const index_1 = require("../utils/index");
12
- const resource_access_1 = require("../utils/resource-access");
13
- const skill_1 = require("../utils/skill");
14
- const secrets_1 = require("../utils/secrets");
15
12
  const opencode_client_1 = require("../utils/opencode-client");
16
13
  const chat_session_1 = require("../utils/chat-session");
17
14
  const assert_1 = require("../utils/assert");
@@ -19,9 +16,10 @@ const redact_1 = require("../utils/redact");
19
16
  const session_abort_1 = require("../utils/session-abort");
20
17
  const chat_session_file_diff_1 = require("../utils/chat-session-file-diff");
21
18
  const chat_usage_1 = require("../utils/chat-usage");
19
+ const scheduler_1 = require("../cronjobs/scheduler");
20
+ const chat_prompt_context_1 = require("../utils/chat-prompt-context");
22
21
  const router = express_1.default.Router({ mergeParams: true });
23
22
  const USER_INSTRUCTIONS_FILENAME = "USER_INSTRUCTIONS.md";
24
- const ACTUAL_USER_MESSAGE_MARKER = "####### Actual user message below #######";
25
23
  function getErrorMessage(error) {
26
24
  if (error && typeof error === 'object' && 'detail' in error) {
27
25
  return String(error.detail);
@@ -121,46 +119,6 @@ async function readWorkspaceUserInstructions() {
121
119
  throw new Error(`Failed to read ${USER_INSTRUCTIONS_FILENAME}: ${nodeError.message}`);
122
120
  }
123
121
  }
124
- async function buildAvailableSkillsPrompt(userId) {
125
- const slugs = (0, skill_1.listSkillSlugs)();
126
- if (slugs.length === 0) {
127
- return null;
128
- }
129
- const availableSkills = (await Promise.all(slugs.map(async (slug) => {
130
- const accessSummary = await (0, resource_access_1.getResourceAccessSummary)("skill", slug, userId);
131
- if (!accessSummary.can_view || !accessSummary.is_approved) {
132
- return null;
133
- }
134
- const { manifest } = await (0, skill_1.readSkillManifestAndEnsurePermissions)(slug);
135
- return {
136
- path: `.agents/skills/${slug}`,
137
- slug,
138
- name: manifest.name,
139
- description: manifest.description,
140
- };
141
- }))).filter((skill) => skill !== null);
142
- if (availableSkills.length === 0) {
143
- return null;
144
- }
145
- const skillLines = availableSkills.map((skill, index) => `${index + 1}. ${skill.name} (${skill.slug})\n path: ${skill.path}\n description: ${skill.description || "(no description provided)"}`);
146
- return `# Available custom skills\n\nThese custom skills are available to you for this session (this is also the result of calling listAvailableSkills at this point in time). Use getSkillContent tool for a specific skill when you need to inspect its SKILL.md before using it.\n\n${skillLines.join("\n\n")}`;
147
- }
148
- async function buildAvailableSecretsPrompt(userId) {
149
- const secretMap = await (0, secrets_1.listResolvedSecretsForUser)(userId);
150
- const keys = Object.keys(secretMap);
151
- if (keys.length === 0) {
152
- return null;
153
- }
154
- return [
155
- "# Available secrets for this user",
156
- "",
157
- "These secret keys are available to the current user for this session. Reuse these exact keys when creating or editing skills and workflows whenever they fit the need.",
158
- "When referring to a secret in skill content or bash commands, use the proxy placeholder format {{SECRET:KEY}} instead of a raw value.",
159
- "If you create a skill or workflow that needs a new secret key not listed here, you may introduce that new key, but you must tell the user to add it in their Profile Secrets before the skill or workflow can be used.",
160
- "",
161
- `Available secret keys: ${keys.join(", ")}`,
162
- ].join("\n");
163
- }
164
122
  async function writeWorkspaceUserInstructions(content) {
165
123
  const workspaceDir = (0, opencode_client_1.getWorkspaceDir)();
166
124
  const userInstructionsPath = path_1.default.join(workspaceDir, USER_INSTRUCTIONS_FILENAME);
@@ -173,11 +131,11 @@ async function writeWorkspaceUserInstructions(content) {
173
131
  }
174
132
  }
175
133
  function stripTextBeforeActualUserMarker(text) {
176
- const markerIndex = text.indexOf(ACTUAL_USER_MESSAGE_MARKER);
134
+ const markerIndex = text.indexOf(chat_prompt_context_1.ACTUAL_USER_MESSAGE_MARKER);
177
135
  if (markerIndex === -1) {
178
136
  return text;
179
137
  }
180
- return text.slice(markerIndex + ACTUAL_USER_MESSAGE_MARKER.length).replace(/^\s+/, "");
138
+ return text.slice(markerIndex + chat_prompt_context_1.ACTUAL_USER_MESSAGE_MARKER.length).replace(/^\s+/, "");
181
139
  }
182
140
  function sanitizeFirstUserMessageForClient(messages) {
183
141
  const firstUserMessageId = messages.find((message) => {
@@ -196,7 +154,7 @@ function sanitizeFirstUserMessageForClient(messages) {
196
154
  return false;
197
155
  }
198
156
  const textPart = part;
199
- return typeof textPart.text === "string" && textPart.text.includes(ACTUAL_USER_MESSAGE_MARKER);
157
+ return typeof textPart.text === "string" && textPart.text.includes(chat_prompt_context_1.ACTUAL_USER_MESSAGE_MARKER);
200
158
  });
201
159
  if (!hasMarker) {
202
160
  return messages;
@@ -217,7 +175,7 @@ function sanitizeFirstUserMessageForClient(messages) {
217
175
  if (markerFound) {
218
176
  return part;
219
177
  }
220
- const markerIndex = textPart.text.indexOf(ACTUAL_USER_MESSAGE_MARKER);
178
+ const markerIndex = textPart.text.indexOf(chat_prompt_context_1.ACTUAL_USER_MESSAGE_MARKER);
221
179
  if (markerIndex === -1) {
222
180
  return {
223
181
  ...textPart,
@@ -402,7 +360,10 @@ function getSessionState(args) {
402
360
  // GET /api/chat/sessions - List user's sessions
403
361
  router.get('/sessions', (0, index_1.apiHandler)(async (req, res) => {
404
362
  const sessions = await client_1.default.chat_sessions.findMany({
405
- where: { user_id: req.userId },
363
+ where: {
364
+ user_id: req.userId,
365
+ visible_to_user: true,
366
+ },
406
367
  orderBy: { updated_at: 'desc' }
407
368
  });
408
369
  if (sessions.length === 0) {
@@ -422,7 +383,7 @@ router.get('/sessions', (0, index_1.apiHandler)(async (req, res) => {
422
383
  await client_1.default.chat_sessions.deleteMany({
423
384
  where: {
424
385
  id: { in: staleSessionIds },
425
- user_id: req.userId
386
+ user_id: req.userId,
426
387
  }
427
388
  });
428
389
  }
@@ -445,14 +406,36 @@ router.get('/sessions', (0, index_1.apiHandler)(async (req, res) => {
445
406
  opencode_session_id: true
446
407
  }
447
408
  });
409
+ const controllableCronjobRuns = await client_1.default.cronjob_runs.findMany({
410
+ where: {
411
+ session_id: { in: validSessions.map((session) => session.id) },
412
+ cronjob: { target_type: "prompt" },
413
+ status: { in: ["running", "paused"] },
414
+ },
415
+ orderBy: { started_at: "desc" },
416
+ select: {
417
+ id: true,
418
+ session_id: true,
419
+ status: true,
420
+ },
421
+ });
422
+ const controllableRunBySessionId = new Map();
423
+ for (const run of controllableCronjobRuns) {
424
+ (0, assert_1.assertCondition)(run.session_id, "Controllable cronjob run is missing chat session id");
425
+ if (controllableRunBySessionId.has(run.session_id))
426
+ continue;
427
+ controllableRunBySessionId.set(run.session_id, run);
428
+ }
448
429
  const enrichedSessions = await Promise.all(validSessions.map(async (session) => {
430
+ const controllableRun = controllableRunBySessionId.get(session.id);
449
431
  const latestAssistantMessageId = await loadLatestAssistantMessageIdForSession(client, session.opencode_session_id);
450
- const hasPendingInput = latestAssistantMessageId !== null && (pendingQuestions.some((question) => question.sessionID === session.opencode_session_id
451
- && question.messageID === latestAssistantMessageId)
452
- || pendingPermissions.some((permission) => permission.sessionID === session.opencode_session_id
453
- && permission.tool?.messageID === latestAssistantMessageId)
454
- || customPendingPermissions.some((permission) => permission.opencode_session_id === session.opencode_session_id
455
- && permission.message_id === latestAssistantMessageId));
432
+ const hasPendingInput = (0, chat_session_1.sessionHasPendingInputForLatestAssistantMessage)({
433
+ opencodeSessionId: session.opencode_session_id,
434
+ latestAssistantMessageId,
435
+ pendingQuestions,
436
+ pendingPermissions,
437
+ customPendingPermissions
438
+ });
456
439
  const rawSessionStatus = (0, chat_session_1.getSessionStatusTypeForSession)(sessionStatusMap, session.opencode_session_id);
457
440
  const sessionState = getSessionState({
458
441
  rawSessionStatus,
@@ -467,7 +450,16 @@ router.get('/sessions', (0, index_1.apiHandler)(async (req, res) => {
467
450
  created_at: session.created_at,
468
451
  updated_at: session.updated_at,
469
452
  state: sessionState.state,
470
- latest_message_id: sessionState.latest_message_id
453
+ latest_message_id: sessionState.latest_message_id,
454
+ cronjob_control: controllableRun
455
+ ? {
456
+ run_id: controllableRun.id,
457
+ status: controllableRun.status,
458
+ can_interrupt: controllableRun.status === "running",
459
+ can_resume: controllableRun.status === "paused",
460
+ can_terminate: true,
461
+ }
462
+ : null,
471
463
  };
472
464
  }));
473
465
  res.json({ sessions: enrichedSessions });
@@ -600,7 +592,7 @@ router.get('/sessions/:id', (0, index_1.apiHandler)(async (req, res) => {
600
592
  }
601
593
  });
602
594
  }, true));
603
- // POST /api/chat/sessions/file-diff/capture-baseline - Capture pre-apply_patch baseline for current opencode session
595
+ // POST /api/chat/sessions/file-diff/capture-baseline - Capture pre-edit baseline for current opencode session
604
596
  router.post('/sessions/file-diff/capture-baseline', (0, index_1.apiHandler)(async (req, res) => {
605
597
  if (!req.opencode_session_id) {
606
598
  throw {
@@ -808,6 +800,21 @@ router.post('/sessions/:id/messages', (0, index_1.apiHandler)(async (req, res) =
808
800
  message: 'Session not found'
809
801
  };
810
802
  }
803
+ const terminalCronjobRun = await client_1.default.cronjob_runs.findFirst({
804
+ where: {
805
+ session_id: id,
806
+ cronjob: { target_type: "prompt" },
807
+ status: { in: ["success", "failed", "terminated", "skipped"] },
808
+ },
809
+ orderBy: { started_at: "desc" },
810
+ select: { status: true },
811
+ });
812
+ if (terminalCronjobRun) {
813
+ throw {
814
+ status: 409,
815
+ message: `This cronjob chat is closed because the run is ${terminalCronjobRun.status}. Start a new chat or rerun the cronjob.`
816
+ };
817
+ }
811
818
  const pendingQuestion = await (0, opencode_client_1.getPendingQuestionForSession)(session.opencode_session_id);
812
819
  if (pendingQuestion) {
813
820
  throw {
@@ -834,28 +841,26 @@ router.post('/sessions/:id/messages', (0, index_1.apiHandler)(async (req, res) =
834
841
  }
835
842
  if ((existingMessagesResult.data || []).length === 0) {
836
843
  const userInstructions = await readWorkspaceUserInstructions();
837
- const availableSkillsPrompt = await buildAvailableSkillsPrompt(req.userId);
838
- const availableSecretsPrompt = await buildAvailableSecretsPrompt(req.userId);
839
- if (userInstructions || availableSkillsPrompt || availableSecretsPrompt) {
840
- const preambleSections = [];
841
- if (userInstructions) {
842
- preambleSections.push(`# Custom user instructions (in case of conflicts, the instructions here take precedence over the contents of the AGENTS.md file)\n\n${userInstructions}`);
843
- }
844
- if (availableSkillsPrompt) {
845
- preambleSections.push(availableSkillsPrompt);
846
- }
847
- if (availableSecretsPrompt) {
848
- preambleSections.push(availableSecretsPrompt);
849
- }
850
- const wrappedUserInstructions = `${preambleSections.join("\n\n")}\n\n${ACTUAL_USER_MESSAGE_MARKER}\n\n`;
851
- finalPromptParts = [
852
- {
853
- type: "text",
854
- text: wrappedUserInstructions
855
- },
856
- ...promptParts
857
- ];
844
+ const availableSkillsPrompt = await (0, chat_prompt_context_1.buildAvailableSkillsPrompt)(req.userId);
845
+ const availableSecretsPrompt = await (0, chat_prompt_context_1.buildAvailableSecretsPrompt)(req.userId);
846
+ const preambleSections = [(0, chat_prompt_context_1.buildCurrentTimePrompt)()];
847
+ if (userInstructions) {
848
+ preambleSections.push(`# Custom user instructions (in case of conflicts, the instructions here take precedence over the contents of the AGENTS.md file)\n\n${userInstructions}`);
849
+ }
850
+ if (availableSkillsPrompt) {
851
+ preambleSections.push(availableSkillsPrompt);
858
852
  }
853
+ if (availableSecretsPrompt) {
854
+ preambleSections.push(availableSecretsPrompt);
855
+ }
856
+ const wrappedUserInstructions = `${preambleSections.join("\n\n")}\n\n${chat_prompt_context_1.ACTUAL_USER_MESSAGE_MARKER}\n\n`;
857
+ finalPromptParts = [
858
+ {
859
+ type: "text",
860
+ text: wrappedUserInstructions
861
+ },
862
+ ...promptParts
863
+ ];
859
864
  }
860
865
  // Use promptAsync to send message and return immediately
861
866
  const result = await client.session.promptAsync({
@@ -1079,9 +1084,27 @@ router.post('/sessions/:id/abort', (0, index_1.apiHandler)(async (req, res) => {
1079
1084
  message: 'Session not found'
1080
1085
  };
1081
1086
  }
1082
- await (0, session_abort_1.abortOpencodeSession)(session.opencode_session_id);
1087
+ const runningCronRun = await client_1.default.cronjob_runs.findFirst({
1088
+ where: {
1089
+ session_id: id,
1090
+ status: "running",
1091
+ },
1092
+ select: {
1093
+ id: true,
1094
+ status: true,
1095
+ opencode_session_id: true,
1096
+ workflow_run_id: true,
1097
+ },
1098
+ });
1099
+ if (runningCronRun) {
1100
+ await (0, scheduler_1.interruptCronjobRun)(runningCronRun.id);
1101
+ }
1102
+ else {
1103
+ await (0, session_abort_1.abortOpencodeSession)(session.opencode_session_id);
1104
+ }
1083
1105
  res.json({
1084
- success: true
1106
+ success: true,
1107
+ cronjob_run_id: runningCronRun?.id ?? null,
1085
1108
  });
1086
1109
  }, true));
1087
1110
  // GET /api/chat/sessions/:id/events - SSE stream for real-time updates
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.startCronJobs = startCronJobs;
4
4
  const cron_1 = require("cron");
5
5
  const resource_reconciliation_1 = require("./resource-reconciliation");
6
+ const scheduler_1 = require("../cronjobs/scheduler");
6
7
  let isCronStarted = false;
7
8
  function startCronJobs() {
8
9
  if (isCronStarted) {
@@ -13,4 +14,5 @@ function startCronJobs() {
13
14
  void (0, resource_reconciliation_1.reconcileResourceMetadataWithFilesystem)();
14
15
  });
15
16
  resourceReconciliationJob.start();
17
+ void (0, scheduler_1.startUserCronjobScheduler)();
16
18
  }