@jive-ai/cli 0.0.46 → 0.0.48

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 (2) hide show
  1. package/dist/index.mjs +161 -78
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -774,6 +774,7 @@ const queries = {
774
774
  gitBranch
775
775
  previewUrl
776
776
  autoApprove
777
+ linkedIssueNumber
777
778
  project {
778
779
  id
779
780
  name
@@ -1072,6 +1073,22 @@ const mutations = {
1072
1073
  }
1073
1074
  }
1074
1075
  }
1076
+ `),
1077
+ CommentCreate: graphql(`
1078
+ mutation CommentCreate($input: CreateCommentInput!) {
1079
+ commentCreate(input: $input) {
1080
+ success
1081
+ comment {
1082
+ id
1083
+ body
1084
+ htmlUrl
1085
+ }
1086
+ errors {
1087
+ message
1088
+ code
1089
+ }
1090
+ }
1091
+ }
1075
1092
  `),
1076
1093
  RegisterRunner: graphql(`
1077
1094
  mutation RegisterRunner($input: RegisterRunnerInput!) {
@@ -1539,7 +1556,8 @@ var ApiClient = class {
1539
1556
  description: data.task.description || "",
1540
1557
  status: data.task.status.toLowerCase().replace("_", "-"),
1541
1558
  gitBranch: data.task.gitBranch || "",
1542
- previewUrl: data.task.previewUrl || void 0
1559
+ previewUrl: data.task.previewUrl || void 0,
1560
+ linkedIssueNumber: data.task.linkedIssueNumber ?? null
1543
1561
  },
1544
1562
  plan: data.task.currentPlan ? {
1545
1563
  id: parseInt(data.task.currentPlan.id, 10),
@@ -1652,6 +1670,22 @@ var ApiClient = class {
1652
1670
  }
1653
1671
  };
1654
1672
  }
1673
+ async createComment(projectId, data) {
1674
+ const result = await (await getGraphQLClient()).request(mutations.CommentCreate, { input: {
1675
+ projectId: String(projectId),
1676
+ issueNumber: data.issueNumber,
1677
+ content: data.content
1678
+ } });
1679
+ if (result.commentCreate.errors?.length) throw this.formatError({ message: result.commentCreate.errors[0].message });
1680
+ return {
1681
+ success: result.commentCreate.success,
1682
+ comment: {
1683
+ id: result.commentCreate.comment.id,
1684
+ body: result.commentCreate.comment.body,
1685
+ htmlUrl: result.commentCreate.comment.htmlUrl
1686
+ }
1687
+ };
1688
+ }
1655
1689
  async getTaskSteps(id) {
1656
1690
  const data = await (await getGraphQLClient()).request(queries.GetPlan, { id: String(id) });
1657
1691
  if (!data.plan?.steps) return [];
@@ -2050,7 +2084,7 @@ async function createGraphQLClient() {
2050
2084
 
2051
2085
  //#endregion
2052
2086
  //#region package.json
2053
- var version = "0.0.46";
2087
+ var version = "0.0.48";
2054
2088
 
2055
2089
  //#endregion
2056
2090
  //#region src/runner/index.ts
@@ -2210,37 +2244,41 @@ var TaskRunner = class {
2210
2244
  }
2211
2245
  }
2212
2246
  async fetchTaskProcess(taskId) {
2213
- const existingProcess = this.taskProcesses[taskId.toString()];
2214
- if (existingProcess) return existingProcess;
2247
+ const key = taskId.toString();
2248
+ const existing = this.taskProcesses[key];
2249
+ if (existing) return existing;
2215
2250
  if (Object.values(this.taskProcesses).filter((p) => p !== null).length >= this.maxConcurrentTasks) {
2216
2251
  console.warn(chalk.yellow(`Maximum concurrent tasks reached (${this.maxConcurrentTasks})`));
2217
2252
  return null;
2218
2253
  }
2254
+ const initPromise = this.initTaskProcess(taskId);
2255
+ this.taskProcesses[key] = initPromise;
2256
+ const result = await initPromise;
2257
+ if (!result && this.taskProcesses[key] === initPromise) this.taskProcesses[key] = null;
2258
+ return result;
2259
+ }
2260
+ async initTaskProcess(taskId) {
2219
2261
  const ctx = await this.fetchTaskContext(taskId);
2220
2262
  if (!ctx) {
2221
2263
  console.error(chalk.red(`Failed to fetch context for task ${taskId}`));
2222
2264
  return null;
2223
2265
  }
2224
- return this.taskProcesses[taskId.toString()] = new Promise((resolve) => {
2225
- (this.config.type === "docker" ? spawnTaskDocker(ctx, this.config, {
2266
+ return {
2267
+ process: this.config.type === "docker" ? await spawnTaskDocker(ctx, this.config, {
2226
2268
  onMessage: (message) => this.onTaskMessage(ctx.taskId, message),
2227
2269
  onError: () => {},
2228
2270
  onClose: () => {
2229
2271
  this.taskProcesses[ctx.taskId.toString()] = null;
2230
2272
  }
2231
- }) : Promise.resolve(spawnTask(ctx, {
2273
+ }) : spawnTask(ctx, {
2232
2274
  onMessage: (message) => this.onTaskMessage(ctx.taskId, message),
2233
2275
  onError: () => {},
2234
2276
  onClose: () => {
2235
2277
  this.taskProcesses[ctx.taskId.toString()] = null;
2236
2278
  }
2237
- }))).then((process$1) => {
2238
- resolve({
2239
- process: process$1,
2240
- ctx
2241
- });
2242
- });
2243
- });
2279
+ }),
2280
+ ctx
2281
+ };
2244
2282
  }
2245
2283
  async sendToTask(taskId, message) {
2246
2284
  const result = await this.fetchTaskProcess(taskId);
@@ -2435,7 +2473,13 @@ var TaskRunner = class {
2435
2473
  `);
2436
2474
  const pendingTasks = (await client.request(query$1, { limit: availableSlots })).tasks.edges.map((e$2) => e$2.node);
2437
2475
  if (pendingTasks.length > 0) console.log(chalk.yellow(`Found ${pendingTasks.length} pending task(s) to pick up`));
2438
- for (const task of pendingTasks) await this.pickupPendingTask(parseInt(task.id));
2476
+ for (const task of pendingTasks) {
2477
+ if (this.taskProcesses[task.id]) {
2478
+ console.log(chalk.dim(`Task ${task.id} already managed, skipping`));
2479
+ continue;
2480
+ }
2481
+ await this.pickupPendingTask(parseInt(task.id));
2482
+ }
2439
2483
  } catch (error$1) {
2440
2484
  if (!error$1.message?.includes("Unable to connect")) console.error(chalk.red(`Failed to poll for pending tasks: ${error$1.message}`));
2441
2485
  }
@@ -2507,17 +2551,10 @@ async function queryClaude(prompt, mcpServer, opts) {
2507
2551
  permissionMode: mapPermissionMode(permissionMode),
2508
2552
  ...betaFlags.length > 0 && { betas: betaFlags },
2509
2553
  canUseTool: async (toolName, input) => {
2510
- if (toolName === "AskUserQuestion") {
2511
- const result = await task.requestUserQuestions(input);
2512
- if (result) return {
2513
- behavior: "allow",
2514
- updatedInput: result
2515
- };
2516
- else return {
2517
- behavior: "deny",
2518
- message: "User did not answer questions in time"
2519
- };
2520
- }
2554
+ if (toolName === "AskUserQuestion") return {
2555
+ behavior: "deny",
2556
+ message: "AskUserQuestion is not allowed. Use the ask_user_question tool from the jive-tasks MCP server instead."
2557
+ };
2521
2558
  if (await task.requestToolPermission(toolName, input)) return {
2522
2559
  behavior: "allow",
2523
2560
  updatedInput: input
@@ -2527,6 +2564,11 @@ async function queryClaude(prompt, mcpServer, opts) {
2527
2564
  message: "User denied this action"
2528
2565
  };
2529
2566
  },
2567
+ systemPrompt: {
2568
+ type: "preset",
2569
+ preset: "claude_code",
2570
+ append: "When you want to use AskUserQuestion, use the ask_user_question tool from the jive-tasks MCP server instead. DO NOT USE THE AskUserQuestion tool directly."
2571
+ },
2530
2572
  stderr: (data) => {
2531
2573
  console.error(data.toString());
2532
2574
  }
@@ -2738,8 +2780,13 @@ function createTasksSdkServer(task) {
2738
2780
 
2739
2781
  If the next step contains a subagent, you must invoke the subagent for the next step.
2740
2782
 
2741
- If result contains \`isComplete: true\`, then the task is complete and you MUST create a pull request using the \`create_pull_request\` tool.
2742
- The user is expecting pull requests to be automatically created when the task is complete.
2783
+ If result contains \`isComplete: true\`, then the task is complete and you MUST add an issue comment using the \`add_issue_comment\` tool.
2784
+ The comment MUST include:
2785
+ - A very concise (1-2 sentence) description of the changes
2786
+ - A reference to the branch: ${task.branch}
2787
+ - A link to the task: https://getjive.app/app/tasks/${task.id}
2788
+
2789
+ The user is expecting issue comments to be automatically created when the task is complete.
2743
2790
  `, {
2744
2791
  stepId: z.number(),
2745
2792
  commitMessage: z.string().describe("Concise commit message describing changes"),
@@ -2803,6 +2850,51 @@ function createTasksSdkServer(task) {
2803
2850
  };
2804
2851
  }
2805
2852
  }),
2853
+ tool("add_issue_comment", "Add a comment to the linked issue on GitHub or GitLab. Use this to post updates, summaries, or task completion notices on the issue.", { content: z.string().describe("The comment body to post on the issue (supports markdown)") }, async (args) => {
2854
+ if (!context) return {
2855
+ content: [{
2856
+ type: "text",
2857
+ text: "Error: Task context not initialized"
2858
+ }],
2859
+ isError: true
2860
+ };
2861
+ try {
2862
+ const apiClient$1 = getApiClient();
2863
+ const { task: taskData, project } = await apiClient$1.getTask(context.taskId);
2864
+ if (!project) return {
2865
+ content: [{
2866
+ type: "text",
2867
+ text: "Error: Project not found for this task"
2868
+ }],
2869
+ isError: true
2870
+ };
2871
+ if (!taskData.linkedIssueNumber) return {
2872
+ content: [{
2873
+ type: "text",
2874
+ text: "Error: No linked issue found for this task. Cannot add comment."
2875
+ }],
2876
+ isError: true
2877
+ };
2878
+ task.debugLog(`Adding comment to issue #${taskData.linkedIssueNumber}`);
2879
+ const result = await apiClient$1.createComment(project.id, {
2880
+ issueNumber: taskData.linkedIssueNumber,
2881
+ content: args.content
2882
+ });
2883
+ task.debugLog(`Comment created: ${JSON.stringify(result, null, 2)}`);
2884
+ return { content: [{
2885
+ type: "text",
2886
+ text: `Comment posted successfully on issue #${taskData.linkedIssueNumber}!\n\nURL: ${result.comment.htmlUrl}`
2887
+ }] };
2888
+ } catch (error$1) {
2889
+ return {
2890
+ content: [{
2891
+ type: "text",
2892
+ text: `Error adding issue comment: ${error$1.message}`
2893
+ }],
2894
+ isError: true
2895
+ };
2896
+ }
2897
+ }),
2806
2898
  tool("create_issue", "Create an issue on GitHub or GitLab for the current project. Use this to track bugs, feature requests, or other work items.", {
2807
2899
  title: z.string().describe("Issue title - a concise summary of the issue"),
2808
2900
  body: z.string().describe("Issue body/description - detailed explanation of the issue"),
@@ -2900,6 +2992,38 @@ function createTasksSdkServer(task) {
2900
2992
  isError: true
2901
2993
  };
2902
2994
  }
2995
+ }),
2996
+ tool("ask_user_question", "USE THIS INSTEAD OF AskUserQuestion. Ask the user clarifying questions. Returns immediately - answers will be provided in a follow-up message.", { questions: z.array(z.object({
2997
+ question: z.string().describe("The question to ask"),
2998
+ header: z.string().describe("Short header/label for the question"),
2999
+ options: z.array(z.object({
3000
+ label: z.string(),
3001
+ description: z.string()
3002
+ })).describe("Options for the user to choose from"),
3003
+ multiSelect: z.boolean().describe("Allow multiple selections")
3004
+ })).describe("Questions to ask the user") }, async (args) => {
3005
+ if (!context) return {
3006
+ content: [{
3007
+ type: "text",
3008
+ text: "Error: Task context not initialized"
3009
+ }],
3010
+ isError: true
3011
+ };
3012
+ try {
3013
+ task.sendUserQuestionRequest(args.questions);
3014
+ return { content: [{
3015
+ type: "text",
3016
+ text: `Questions sent to user. Their answers will be provided in a follow-up message. You may continue with other work while waiting.`
3017
+ }] };
3018
+ } catch (error$1) {
3019
+ return {
3020
+ content: [{
3021
+ type: "text",
3022
+ text: `Error: ${error$1.message}`
3023
+ }],
3024
+ isError: true
3025
+ };
3026
+ }
2903
3027
  })
2904
3028
  ]
2905
3029
  });
@@ -3068,7 +3192,6 @@ var Task = class {
3068
3192
  heartbeatInterval = null;
3069
3193
  pendingAcks = /* @__PURE__ */ new Map();
3070
3194
  pendingPermissions = /* @__PURE__ */ new Map();
3071
- pendingQuestions = /* @__PURE__ */ new Map();
3072
3195
  tunnels = /* @__PURE__ */ new Map();
3073
3196
  proxies = /* @__PURE__ */ new Map();
3074
3197
  primaryTunnelHost = null;
@@ -3088,6 +3211,9 @@ var Task = class {
3088
3211
  }
3089
3212
  return tasksConfig.git.default_branch;
3090
3213
  }
3214
+ get branch() {
3215
+ return this.ctx.branch;
3216
+ }
3091
3217
  get projectId() {
3092
3218
  return this.ctx.projectId;
3093
3219
  }
@@ -3243,10 +3369,6 @@ var Task = class {
3243
3369
  this.handleToolPermissionResponse(inputMessage.payload);
3244
3370
  continue;
3245
3371
  }
3246
- if (inputMessage.type === "user_question_response") {
3247
- this.handleUserQuestionResponse(inputMessage.payload);
3248
- continue;
3249
- }
3250
3372
  if (this.status !== "idle") {
3251
3373
  this.debugLog(`Queueing message (status: ${this.status}): ${inputMessage.type}`);
3252
3374
  this.queuedMessages.push(inputMessage);
@@ -3301,43 +3423,21 @@ var Task = class {
3301
3423
  return approved;
3302
3424
  }
3303
3425
  /**
3304
- * Request user to answer questions. Returns promise that resolves to { questions, answers } or null.
3305
- * Auto-denies (returns null) after 60 seconds if no response received.
3426
+ * Send user questions to the server (non-blocking).
3427
+ * Answers will come back as a user message via promptSendToRunner.
3306
3428
  */
3307
- async requestUserQuestions(input) {
3429
+ sendUserQuestionRequest(questions) {
3308
3430
  const requestId = crypto.randomUUID();
3309
- const TIMEOUT_MS = 6e4 * 60 * 24;
3310
- const questionPromise = new Promise((resolve) => {
3311
- const timeout = setTimeout(() => {
3312
- this.debugLog(`User question request ${requestId} timed out (24h)`);
3313
- this.pendingQuestions.delete(requestId);
3314
- resolve(null);
3315
- }, TIMEOUT_MS);
3316
- this.pendingQuestions.set(requestId, {
3317
- resolve,
3318
- timeout
3319
- });
3320
- });
3321
3431
  this.sendToTaskRunner({
3322
3432
  type: "user_question_request",
3323
3433
  payload: {
3324
3434
  requestId,
3325
3435
  sessionId: this.ctx.sessionId,
3326
- questions: input.questions,
3327
- expiresAt: new Date(Date.now() + TIMEOUT_MS).toISOString()
3436
+ questions,
3437
+ expiresAt: new Date(Date.now() + 6e4 * 60 * 24).toISOString()
3328
3438
  }
3329
3439
  });
3330
- this.debugLog(`Waiting for user to answer ${input.questions.length} question(s)...`);
3331
- const answers = await questionPromise;
3332
- if (!answers) {
3333
- this.debugLog(`User did not answer questions in time`);
3334
- return null;
3335
- }
3336
- this.debugLog(`User answered questions`);
3337
- return {
3338
- questions: input.questions,
3339
- answers
3340
- };
3440
+ this.debugLog(`Sent ${questions.length} question(s) to user (requestId: ${requestId})`);
3341
3441
  }
3342
3442
  /**
3343
3443
  * Handle tool permission response from runner
@@ -3353,20 +3453,6 @@ var Task = class {
3353
3453
  pending.resolve(payload.approved);
3354
3454
  this.pendingPermissions.delete(payload.requestId);
3355
3455
  }
3356
- /**
3357
- * Handle user question response from runner
3358
- */
3359
- handleUserQuestionResponse(payload) {
3360
- const pending = this.pendingQuestions.get(payload.requestId);
3361
- if (!pending) {
3362
- this.debugLog(`Received question response for unknown request: ${payload.requestId}`);
3363
- return;
3364
- }
3365
- this.updateLastActivity();
3366
- clearTimeout(pending.timeout);
3367
- pending.resolve(payload.answers);
3368
- this.pendingQuestions.delete(payload.requestId);
3369
- }
3370
3456
  processMessage(inputMessage) {
3371
3457
  this.updateLastActivity();
3372
3458
  switch (inputMessage.type) {
@@ -3379,9 +3465,6 @@ var Task = class {
3379
3465
  case "tool_permission_response":
3380
3466
  this.handleToolPermissionResponse(inputMessage.payload);
3381
3467
  break;
3382
- case "user_question_response":
3383
- this.handleUserQuestionResponse(inputMessage.payload);
3384
- break;
3385
3468
  }
3386
3469
  }
3387
3470
  processQueuedMessages() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@jive-ai/cli",
4
- "version": "0.0.46",
4
+ "version": "0.0.48",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "dist",
@@ -15,7 +15,7 @@
15
15
  "test": "echo \"Error: no test specified\" && exit 1",
16
16
  "typecheck": "tsc --noEmit",
17
17
  "build": "tsdown && npm pack && npm install -g jive-ai-cli-*.tgz",
18
- "docker:clean": "docker rmi jiveai/task:latest jiveai/task:$npm_package_version",
18
+ "docker:clean": "docker rmi jiveai/task:latest jiveai/task:$npm_package_version || true",
19
19
  "docker:build": "bun run build && npm run docker:clean && .docker/build.sh",
20
20
  "docker:push": ".docker/build.sh --push",
21
21
  "prepublishOnly": "npm run typecheck && npm run build"