@crewx/cli 0.8.9-rc.9 → 0.9.0-rc.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.
@@ -100,20 +100,26 @@ function checkLogsDir() {
100
100
  }
101
101
  /**
102
102
  * Check CLI provider availability.
103
+ * Providers are checked in PROVIDER_ORDER: codex → claude → opencode → antigravity → copilot.
103
104
  */
104
105
  function checkCliProviders() {
105
- const providers = [
106
- { name: 'claude', cmd: 'claude', install: 'npm install -g @anthropic-ai/claude-code' },
107
- { name: 'gemini', cmd: 'gemini', install: 'npm install -g @google/gemini-cli' },
108
- { name: 'copilot (gh)', cmd: 'gh', install: 'brew install gh # or visit cli.github.com' },
109
- ];
110
- return providers.map(p => {
111
- const available = isCommandAvailable(p.cmd);
106
+ // Map each provider (in PROVIDER_ORDER) to its CLI command and install hint.
107
+ const providerInfo = {
108
+ codex: { cmd: 'codex', install: 'npm install -g @openai/codex' },
109
+ claude: { cmd: 'claude', install: 'npm install -g @anthropic-ai/claude-code' },
110
+ opencode: { cmd: 'opencode', install: 'npm install -g opencode' },
111
+ antigravity: { cmd: 'antigravity', install: 'npm install -g antigravity' },
112
+ copilot: { cmd: 'gh', install: 'brew install gh # or visit cli.github.com' },
113
+ };
114
+ return sdk_1.PROVIDER_ORDER.map(provider => {
115
+ const info = providerInfo[provider];
116
+ const displayName = provider === 'copilot' ? 'copilot (gh)' : provider;
117
+ const available = isCommandAvailable(info.cmd);
112
118
  return {
113
- name: `${p.name.toUpperCase()} CLI`,
119
+ name: `${displayName.toUpperCase()} CLI`,
114
120
  status: available ? 'success' : 'warning',
115
121
  message: available ? 'Installed and available' : 'Not installed or not on PATH',
116
- details: available ? undefined : `Install: ${p.install}`,
122
+ details: available ? undefined : `Install: ${info.install}`,
117
123
  };
118
124
  });
119
125
  }
@@ -13,7 +13,7 @@ exports.KNOWN_COMMANDS = new Set([
13
13
  'q', 'query',
14
14
  'x', 'execute',
15
15
  'agent',
16
- 'ps', 'kill', 'result', 'log',
16
+ 'ps', 'kill', 'result', 'log', 'restart',
17
17
  'doctor', 'init',
18
18
  'db',
19
19
  'help',
@@ -0,0 +1,23 @@
1
+ /**
2
+ * crewx restart handler.
3
+ * Restarts a failed task as a new task, reusing the original task's
4
+ * thread / agent / mode / model / provider. The `provider` column is not
5
+ * persisted on the task row, but the SDK records the effective provider in
6
+ * `metadata.provider`, so we recover it from there (falling back to the
7
+ * agent's configured provider when absent).
8
+ *
9
+ * Unlike the Web UI `Retry` (fire-and-forget), CLI `restart` awaits the new
10
+ * task to completion and prints the final response — the CLI process is the
11
+ * tracking owner, so it must not exit before the task finishes.
12
+ *
13
+ * Usage:
14
+ * crewx restart <task-id>
15
+ *
16
+ * Behavior:
17
+ * - Only tasks with status='failed' can be restarted.
18
+ * - A new taskId is generated; the original task row is never mutated.
19
+ * - metadata.restartedFromTaskId (+ retriedFromTaskId for compat) is recorded.
20
+ * - The Web UI `TSK.RETRY` system-message prefix is reused verbatim.
21
+ * - stdout carries the agent final response; guidance/errors go to stderr.
22
+ */
23
+ export declare function handleRestart(args: string[]): Promise<void>;
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * crewx restart handler.
4
+ * Restarts a failed task as a new task, reusing the original task's
5
+ * thread / agent / mode / model / provider. The `provider` column is not
6
+ * persisted on the task row, but the SDK records the effective provider in
7
+ * `metadata.provider`, so we recover it from there (falling back to the
8
+ * agent's configured provider when absent).
9
+ *
10
+ * Unlike the Web UI `Retry` (fire-and-forget), CLI `restart` awaits the new
11
+ * task to completion and prints the final response — the CLI process is the
12
+ * tracking owner, so it must not exit before the task finishes.
13
+ *
14
+ * Usage:
15
+ * crewx restart <task-id>
16
+ *
17
+ * Behavior:
18
+ * - Only tasks with status='failed' can be restarted.
19
+ * - A new taskId is generated; the original task row is never mutated.
20
+ * - metadata.restartedFromTaskId (+ retriedFromTaskId for compat) is recorded.
21
+ * - The Web UI `TSK.RETRY` system-message prefix is reused verbatim.
22
+ * - stdout carries the agent final response; guidance/errors go to stderr.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.handleRestart = handleRestart;
26
+ const sdk_1 = require("@crewx/sdk");
27
+ const crewx_cli_1 = require("../bootstrap/crewx-cli");
28
+ const inherited_trace_1 = require("../utils/inherited-trace");
29
+ /**
30
+ * Build the retry system-message prefix.
31
+ * Intentionally identical to the server `ThreadService.retryTask()` prefix so
32
+ * stored conversations / parsers stay compatible. `type="retry"` is preserved.
33
+ */
34
+ function buildRetryPrefix(originalTaskId) {
35
+ return (`<crewx_system_message type="retry">\n` +
36
+ `Retrying a failed task.\n` +
37
+ `You must run \`crewx dreaming --task=${originalTaskId}\` to review where it stopped before continuing.\n` +
38
+ `</crewx_system_message>\n\n`);
39
+ }
40
+ /**
41
+ * Recover the effective provider from a task row's `metadata` JSON.
42
+ * The SDK persists the resolved provider as `metadata.provider`. Returns
43
+ * the string when present and non-empty, otherwise `undefined` so the
44
+ * agent's configured provider default is used.
45
+ */
46
+ function extractProvider(metadata) {
47
+ if (!metadata)
48
+ return undefined;
49
+ try {
50
+ const parsed = JSON.parse(metadata);
51
+ const provider = parsed.provider;
52
+ return typeof provider === 'string' && provider.length > 0 ? provider : undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ async function handleRestart(args) {
59
+ const verbose = args.includes('--verbose');
60
+ const taskId = args.find((a) => !a.startsWith('--'));
61
+ if (!taskId) {
62
+ process.stderr.write('Usage: crewx restart <task-id>\n');
63
+ process.exit(1);
64
+ return;
65
+ }
66
+ const repo = new sdk_1.TaskRepository();
67
+ const original = repo.getTask(taskId);
68
+ if (!original) {
69
+ process.stderr.write(`Error: Task not found: ${taskId}\n`);
70
+ process.exit(1);
71
+ return;
72
+ }
73
+ if (original.status !== 'failed') {
74
+ process.stderr.write(`Error: Task ${taskId} is not in failed state (current: ${original.status ?? 'unknown'}). ` +
75
+ `Only failed tasks can be restarted.\n`);
76
+ process.exit(1);
77
+ return;
78
+ }
79
+ if (!original.prompt) {
80
+ process.stderr.write(`Error: Task ${taskId} has no prompt to restart.\n`);
81
+ process.exit(1);
82
+ return;
83
+ }
84
+ if (!original.agent_id) {
85
+ process.stderr.write(`Error: Task ${taskId} has no agent to restart.\n`);
86
+ process.exit(1);
87
+ return;
88
+ }
89
+ const mode = original.mode === 'query' ? 'query' : 'execute';
90
+ const agentRef = `@${original.agent_id}`;
91
+ const newTaskId = (0, sdk_1.generateId)('tsk');
92
+ const restartPrompt = `${buildRetryPrefix(taskId)}${original.prompt}`;
93
+ // model: reuse the original task row value when present, otherwise fall back
94
+ // to the agent's configured default (resolved inside Crewx).
95
+ const model = original.model ?? undefined;
96
+ // provider: recovered from metadata.provider (SDK-persisted). Falls back to
97
+ // the agent's configured provider when absent. This preserves a
98
+ // `crewx x --provider ...` choice across restart.
99
+ const provider = extractProvider(original.metadata);
100
+ (0, sdk_1.setAuditVerbose)(verbose);
101
+ if (verbose) {
102
+ process.stderr.write(`🔁 Restart: ${taskId} → ${newTaskId}\n`);
103
+ process.stderr.write(`🤖 Agent: ${agentRef} Mode: ${mode}\n`);
104
+ if (original.thread_id)
105
+ process.stderr.write(`🔗 Thread: ${original.thread_id}\n`);
106
+ process.stderr.write('─'.repeat(60) + '\n');
107
+ }
108
+ const crewx = await (0, crewx_cli_1.createCliCrewx)();
109
+ const sdkOptions = {
110
+ taskId: newTaskId,
111
+ threadId: original.thread_id ?? undefined,
112
+ model,
113
+ provider,
114
+ metadata: {
115
+ restartedFromTaskId: taskId,
116
+ // Back-compat / search convenience: mirror the Web UI metadata key.
117
+ retriedFromTaskId: taskId,
118
+ },
119
+ trace: (0, inherited_trace_1.readInheritedTrace)(),
120
+ };
121
+ let exitCode = 0;
122
+ try {
123
+ const result = mode === 'query'
124
+ ? await crewx.query(agentRef, restartPrompt, sdkOptions)
125
+ : await crewx.execute(agentRef, restartPrompt, sdkOptions);
126
+ if (result.ok) {
127
+ process.stdout.write(result.data + '\n');
128
+ }
129
+ else {
130
+ const errMsg = result.error?.message ?? 'Restart failed';
131
+ const resultTaskId = result.meta?.taskId ?? newTaskId;
132
+ process.stderr.write(`Error: ${errMsg}\n`);
133
+ process.stderr.write(`taskId=${resultTaskId}\n`);
134
+ exitCode = 1;
135
+ }
136
+ }
137
+ catch (err) {
138
+ const errMsg = err instanceof Error ? err.message : String(err);
139
+ process.stderr.write(`Error: ${errMsg}\n`);
140
+ process.stderr.write(`taskId=${newTaskId}\n`);
141
+ exitCode = 1;
142
+ }
143
+ finally {
144
+ await crewx.close();
145
+ }
146
+ if (exitCode !== 0)
147
+ process.exit(exitCode);
148
+ }
package/dist/main.js CHANGED
@@ -55,6 +55,7 @@ const agent_1 = require("./commands/agent");
55
55
  const ps_1 = require("./commands/ps");
56
56
  const kill_1 = require("./commands/kill");
57
57
  const result_1 = require("./commands/result");
58
+ const restart_1 = require("./commands/restart");
58
59
  const log_1 = require("./commands/log");
59
60
  const doctor_1 = require("./commands/doctor");
60
61
  const init_1 = require("./commands/init");
@@ -133,6 +134,10 @@ async function main() {
133
134
  case 'result':
134
135
  await (0, result_1.handleResult)(args.slice(1));
135
136
  return;
137
+ // CLI Task Lifecycle 2단계: restart a failed task as a new task
138
+ case 'restart':
139
+ await (0, restart_1.handleRestart)(args.slice(1));
140
+ return;
136
141
  // P1-1: log
137
142
  case 'log':
138
143
  await (0, log_1.handleLog)(args.slice(1));
@@ -269,6 +274,7 @@ Task Management:
269
274
  kill <task-id> Kill a running task
270
275
  kill --all Kill all running tasks
271
276
  result [task-id] Get task result (or list recent tasks)
277
+ restart <task-id> Restart a failed task as a new task
272
278
 
273
279
  Logs & Diagnostics:
274
280
  log [ls|<task-id>] View task logs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cli",
3
- "version": "0.8.9-rc.9",
3
+ "version": "0.9.0-rc.0",
4
4
  "license": "UNLICENSED",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -24,17 +24,17 @@
24
24
  "@crewx/adapter-slack": "0.1.4",
25
25
  "better-sqlite3": "*",
26
26
  "isomorphic-git": "1.37.1",
27
- "@crewx/sdk": "0.8.9-rc.9",
28
- "@crewx/search": "0.1.10-rc.8",
29
- "@crewx/memory": "0.1.23-rc.25",
30
- "@crewx/wbs": "0.1.10-rc.38",
31
- "@crewx/doc": "0.1.9-rc.7",
32
- "@crewx/cron": "0.1.10-rc.43",
33
- "@crewx/skill": "0.1.20",
34
- "@crewx/wi": "0.1.10-rc.9",
35
- "@crewx/chromex": "0.1.0-rc.45",
36
- "@crewx/workflow": "0.3.22-rc.25",
37
- "@crewx/shared": "0.0.5"
27
+ "@crewx/sdk": "0.9.0-rc.0",
28
+ "@crewx/search": "0.1.10",
29
+ "@crewx/doc": "0.1.9",
30
+ "@crewx/memory": "0.1.23",
31
+ "@crewx/wbs": "0.1.10",
32
+ "@crewx/cron": "0.1.10",
33
+ "@crewx/wi": "0.1.10",
34
+ "@crewx/chromex": "0.1.0",
35
+ "@crewx/workflow": "0.3.22",
36
+ "@crewx/shared": "0.0.6",
37
+ "@crewx/skill": "0.1.20"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/better-sqlite3": "*",