@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.
- package/dist/index.mjs +161 -78
- 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.
|
|
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
|
|
2214
|
-
|
|
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
|
|
2225
|
-
|
|
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
|
-
}) :
|
|
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
|
-
})
|
|
2238
|
-
|
|
2239
|
-
|
|
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)
|
|
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
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
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
|
|
2742
|
-
The
|
|
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
|
-
*
|
|
3305
|
-
*
|
|
3426
|
+
* Send user questions to the server (non-blocking).
|
|
3427
|
+
* Answers will come back as a user message via promptSendToRunner.
|
|
3306
3428
|
*/
|
|
3307
|
-
|
|
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
|
|
3327
|
-
expiresAt: new Date(Date.now() +
|
|
3436
|
+
questions,
|
|
3437
|
+
expiresAt: new Date(Date.now() + 6e4 * 60 * 24).toISOString()
|
|
3328
3438
|
}
|
|
3329
3439
|
});
|
|
3330
|
-
this.debugLog(`
|
|
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.
|
|
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"
|