@the-open-engine/zeroshot 6.31.3 → 6.32.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.
- package/README.md +66 -98
- package/cli/index.js +251 -252
- package/cli/lib/setup-provider-readiness.js +86 -0
- package/cli/lib/setup-scanner-worker.js +120 -0
- package/cli/lib/setup-scanner.js +185 -0
- package/cli/lib/setup-wizard-input.js +146 -0
- package/cli/lib/setup-wizard-model.js +205 -0
- package/cli/lib/setup-wizard-plan-view.js +157 -0
- package/cli/lib/setup-wizard-scan-view.js +144 -0
- package/cli/lib/setup-wizard-terminal.js +237 -0
- package/cli/lib/setup-wizard-view.js +180 -0
- package/cli/lib/setup-wizard.js +281 -0
- package/cli/message-formatters-normal.js +14 -18
- package/cli/message-formatters-watch.js +53 -141
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +8 -2
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +4 -2
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +13 -3
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +7 -4
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +2 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/completion.js +102 -153
- package/lib/settings.js +10 -2
- package/lib/setup-apply.js +62 -55
- package/lib/setup-plan.js +32 -52
- package/lib/start-cluster.js +65 -25
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -3
- package/scripts/postinstall.js +54 -0
- package/src/agent/agent-lifecycle.js +11 -1
- package/src/agent/agent-task-executor.js +25 -7
- package/src/agent/structured-output-error.js +42 -0
- package/src/agent-cli-provider/adapters/codex.ts +8 -12
- package/src/agent-cli-provider/provider-registry.ts +15 -3
- package/src/agent-cli-provider/single-agent-runtime.ts +11 -11
- package/src/agent-cli-provider/types.ts +2 -0
- package/src/preflight.js +27 -1
- package/src/status-footer.js +19 -12
- package/task-lib/commands/list.js +90 -78
- package/task-lib/commands/status.js +97 -40
- package/task-lib/effective-status.js +52 -0
|
@@ -52,6 +52,10 @@ const {
|
|
|
52
52
|
validateCompletedResumeIdentity,
|
|
53
53
|
} = require('./provider-session');
|
|
54
54
|
const { extractClaudeVertexModelError } = require('./output-extraction');
|
|
55
|
+
const {
|
|
56
|
+
createStructuredOutputInvalidError,
|
|
57
|
+
isStructuredOutputInvalidError,
|
|
58
|
+
} = require('./structured-output-error');
|
|
55
59
|
const TASK_TERMINAL_STATUSES = new Set(['completed', 'failed', 'killed', 'stale']);
|
|
56
60
|
function runCommandWithTimeout(command, args, options = {}, callback = null) {
|
|
57
61
|
const timeout = options.timeout ?? 30000;
|
|
@@ -1412,6 +1416,7 @@ async function evaluateStructuredSuccess({ agent, taskId, state, success, allowR
|
|
|
1412
1416
|
return { success: true, error: null };
|
|
1413
1417
|
} catch (error) {
|
|
1414
1418
|
if (
|
|
1419
|
+
isStructuredOutputInvalidError(error) ||
|
|
1415
1420
|
isNestedLifecycleError(error) ||
|
|
1416
1421
|
error?.permanent === true ||
|
|
1417
1422
|
error?.recoveryAbort === true
|
|
@@ -2858,7 +2863,10 @@ async function parseResultOutput(agent, output, { allowRecovery = true } = {}) {
|
|
|
2858
2863
|
const schema = agent.config.jsonSchema;
|
|
2859
2864
|
if (!schema) {
|
|
2860
2865
|
if (parsed) return parsed;
|
|
2861
|
-
throw
|
|
2866
|
+
throw createStructuredOutputInvalidError(
|
|
2867
|
+
`Agent ${agent.id} output missing required JSON block`,
|
|
2868
|
+
'missing_json'
|
|
2869
|
+
);
|
|
2862
2870
|
}
|
|
2863
2871
|
|
|
2864
2872
|
const { createStructuredOutputValidator, reformatOutput } = require('./output-reformatter');
|
|
@@ -2911,7 +2919,12 @@ async function parseResultOutput(agent, output, { allowRecovery = true } = {}) {
|
|
|
2911
2919
|
throw new Error('Task execution failed - no output');
|
|
2912
2920
|
}
|
|
2913
2921
|
const recoveryDetail = recovery?.lastError ? ` Recovery exhausted: ${recovery.lastError}.` : '';
|
|
2914
|
-
throw
|
|
2922
|
+
throw createStructuredOutputInvalidError(
|
|
2923
|
+
`Agent ${agent.id} output missing required JSON block.${recoveryDetail}`,
|
|
2924
|
+
'missing_json',
|
|
2925
|
+
directValidation,
|
|
2926
|
+
recovery
|
|
2927
|
+
);
|
|
2915
2928
|
}
|
|
2916
2929
|
|
|
2917
2930
|
if (!allowRecovery) {
|
|
@@ -2921,13 +2934,18 @@ async function parseResultOutput(agent, output, { allowRecovery = true } = {}) {
|
|
|
2921
2934
|
|
|
2922
2935
|
const errorDetail = directValidation?.error || 'unknown schema error';
|
|
2923
2936
|
const message = `Agent ${agent.id} output failed JSON schema validation: ${errorDetail}`;
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2937
|
+
const terminalStructuredRole = ['planner', 'conductor', 'validator'].includes(agent.role);
|
|
2938
|
+
if (recovery?.status === 'exhausted' && terminalStructuredRole) {
|
|
2939
|
+
throw createStructuredOutputInvalidError(
|
|
2940
|
+
`${message}. Recovery exhausted after ${recovery.attempts} attempts: ${recovery.lastError}`,
|
|
2941
|
+
'schema_validation',
|
|
2942
|
+
directValidation,
|
|
2943
|
+
recovery
|
|
2929
2944
|
);
|
|
2930
2945
|
}
|
|
2946
|
+
if (agent.role === 'validator') {
|
|
2947
|
+
throw new Error(message);
|
|
2948
|
+
}
|
|
2931
2949
|
|
|
2932
2950
|
console.warn(`⚠️ ${message}`);
|
|
2933
2951
|
agent._publish({
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const STRUCTURED_OUTPUT_INVALID_CODE = 'STRUCTURED_OUTPUT_INVALID';
|
|
2
|
+
|
|
3
|
+
function createStructuredOutputInvalidError(message, kind, validation = null, recovery = null) {
|
|
4
|
+
const error = new Error(message);
|
|
5
|
+
error.code = STRUCTURED_OUTPUT_INVALID_CODE;
|
|
6
|
+
error.details = {
|
|
7
|
+
kind,
|
|
8
|
+
validationError: validation?.error ?? null,
|
|
9
|
+
recoveryAttempts: recovery?.status === 'exhausted' ? recovery.attempts : 0,
|
|
10
|
+
recoveryError: recovery?.status === 'exhausted' ? recovery.lastError : null,
|
|
11
|
+
};
|
|
12
|
+
return error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isStructuredOutputInvalidError(error) {
|
|
16
|
+
return error?.code === STRUCTURED_OUTPUT_INVALID_CODE;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function buildStructuredOutputClusterFailure(agent, error) {
|
|
20
|
+
return {
|
|
21
|
+
topic: 'CLUSTER_FAILED',
|
|
22
|
+
receiver: 'broadcast',
|
|
23
|
+
content: {
|
|
24
|
+
text: `Cluster failed: structured output is invalid for ${agent.id} - ${error.message}`,
|
|
25
|
+
data: {
|
|
26
|
+
reason: 'structured_output_invalid',
|
|
27
|
+
agentId: agent.id,
|
|
28
|
+
role: agent.role,
|
|
29
|
+
code: error.code,
|
|
30
|
+
details: error.details ?? null,
|
|
31
|
+
error: error.message,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
STRUCTURED_OUTPUT_INVALID_CODE,
|
|
39
|
+
createStructuredOutputInvalidError,
|
|
40
|
+
isStructuredOutputInvalidError,
|
|
41
|
+
buildStructuredOutputClusterFailure,
|
|
42
|
+
};
|
|
@@ -255,7 +255,9 @@ function assertStructuredOutputRecoveryFeatures(options: BuildProviderCommandOpt
|
|
|
255
255
|
const missing = [
|
|
256
256
|
['--sandbox', features.supportsSandbox],
|
|
257
257
|
['--ephemeral', features.supportsEphemeral],
|
|
258
|
-
|
|
258
|
+
...(options.trustIsolatedCodexProfile === true
|
|
259
|
+
? []
|
|
260
|
+
: [['--ignore-user-config', features.supportsIgnoreUserConfig] as const]),
|
|
259
261
|
['--ignore-rules', features.supportsIgnoreRules],
|
|
260
262
|
['--strict-config', features.supportsStrictConfig],
|
|
261
263
|
['--config', features.supportsConfigOverride],
|
|
@@ -284,17 +286,11 @@ function buildStructuredOutputRecoveryCommand(
|
|
|
284
286
|
const args = [...spec.args];
|
|
285
287
|
const prompt = args.pop();
|
|
286
288
|
if (prompt === undefined) throw new Error('Codex recovery command is missing its prompt');
|
|
287
|
-
args.push(
|
|
288
|
-
|
|
289
|
-
'
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
'--ignore-rules',
|
|
293
|
-
'--strict-config',
|
|
294
|
-
'--config',
|
|
295
|
-
'web_search="disabled"',
|
|
296
|
-
prompt
|
|
297
|
-
);
|
|
289
|
+
args.push('--sandbox', 'read-only', '--ephemeral');
|
|
290
|
+
if (options.trustIsolatedCodexProfile !== true) {
|
|
291
|
+
args.push('--ignore-user-config');
|
|
292
|
+
}
|
|
293
|
+
args.push('--ignore-rules', '--strict-config', '--config', 'web_search="disabled"', prompt);
|
|
298
294
|
return { ...spec, args };
|
|
299
295
|
}
|
|
300
296
|
|
|
@@ -276,9 +276,9 @@ export const providerRegistry = [
|
|
|
276
276
|
authInstructions: 'codex login',
|
|
277
277
|
credentialPaths: ['~/.config/codex', '~/.codex'],
|
|
278
278
|
credentialEnvKeys: codexAdapter.credentialEnvKeys,
|
|
279
|
-
settingsFields: ['webSearch'],
|
|
280
|
-
settingsDefaults: { webSearch: false },
|
|
281
|
-
settingsValidator:
|
|
279
|
+
settingsFields: ['webSearch', 'trustIsolatedRecoveryProfile'],
|
|
280
|
+
settingsDefaults: { webSearch: false, trustIsolatedRecoveryProfile: false },
|
|
281
|
+
settingsValidator: validateCodexSettings,
|
|
282
282
|
capabilities: {
|
|
283
283
|
...STANDARD_CAPABILITIES,
|
|
284
284
|
jsonSchema: true,
|
|
@@ -662,6 +662,18 @@ export const providerRegistry = [
|
|
|
662
662
|
},
|
|
663
663
|
] as const satisfies readonly ProviderRegistryEntry[];
|
|
664
664
|
|
|
665
|
+
function validateCodexSettings(settings: Record<string, unknown>): string | null {
|
|
666
|
+
const webSearchError = validateWebSearchSettings('codex', settings);
|
|
667
|
+
if (webSearchError) return webSearchError;
|
|
668
|
+
if (
|
|
669
|
+
settings.trustIsolatedRecoveryProfile === undefined ||
|
|
670
|
+
typeof settings.trustIsolatedRecoveryProfile === 'boolean'
|
|
671
|
+
) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
return 'providerSettings.codex.trustIsolatedRecoveryProfile must be a boolean';
|
|
675
|
+
}
|
|
676
|
+
|
|
665
677
|
function validateWebSearchSettings(
|
|
666
678
|
provider: 'codex' | 'opencode',
|
|
667
679
|
settings: Record<string, unknown>
|
|
@@ -54,16 +54,12 @@ import type {
|
|
|
54
54
|
|
|
55
55
|
type UnknownFunction = (...args: readonly unknown[]) => unknown;
|
|
56
56
|
|
|
57
|
-
interface CommandParts {
|
|
58
|
-
readonly command: string;
|
|
59
|
-
readonly args: readonly string[];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
57
|
interface RuntimeProviderSettings {
|
|
63
58
|
readonly defaultLevel?: ModelLevel;
|
|
64
59
|
readonly levelOverrides: LevelOverrides;
|
|
65
60
|
readonly gateway?: GatewayBuildOptions;
|
|
66
61
|
readonly webSearch?: boolean;
|
|
62
|
+
readonly trustIsolatedRecoveryProfile?: boolean;
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
interface RuntimeCommandContext {
|
|
@@ -428,7 +424,6 @@ function ompExecutionContext(
|
|
|
428
424
|
throw new Error('options.executionContext must be "host", "detached", "docker", or "benchmark".');
|
|
429
425
|
}
|
|
430
426
|
|
|
431
|
-
|
|
432
427
|
function ompSdkOutputContract(
|
|
433
428
|
options: BuildProviderCommandOptions
|
|
434
429
|
):
|
|
@@ -664,7 +659,7 @@ export function probeRuntimeProviderCli(
|
|
|
664
659
|
const requested = registryEntry.settingsFields.includes('webSearch')
|
|
665
660
|
? runtimeProviderSettings(settings, adapter.id, process.cwd()).webSearch === true
|
|
666
661
|
: false;
|
|
667
|
-
const helpCommand =
|
|
662
|
+
const helpCommand = resolveProviderCommand(adapter.id);
|
|
668
663
|
const commandAvailable =
|
|
669
664
|
evidence === undefined
|
|
670
665
|
? booleanResult(commandExistsFn(helpCommand.command))
|
|
@@ -735,6 +730,10 @@ function buildRuntimeOptions(
|
|
|
735
730
|
cliFeatures: runtime.cliFeatures,
|
|
736
731
|
};
|
|
737
732
|
const resolved = { ...baseResolved };
|
|
733
|
+
delete resolved.trustIsolatedCodexProfile;
|
|
734
|
+
if (baseOptions.structuredOutputRecovery && adapter.id === 'codex') {
|
|
735
|
+
resolved.trustIsolatedCodexProfile = providerSettings.trustIsolatedRecoveryProfile === true;
|
|
736
|
+
}
|
|
738
737
|
if (baseOptions.structuredOutputRecovery) {
|
|
739
738
|
delete resolved.resumeSessionId;
|
|
740
739
|
delete resolved.continueSession;
|
|
@@ -870,6 +869,10 @@ function runtimeProviderSettings(
|
|
|
870
869
|
providerSettings.webSearch,
|
|
871
870
|
`settings.providerSettings.${provider}.webSearch`
|
|
872
871
|
);
|
|
872
|
+
const trustIsolatedRecoveryProfile = optionalBoolean(
|
|
873
|
+
providerSettings.trustIsolatedRecoveryProfile,
|
|
874
|
+
`settings.providerSettings.${provider}.trustIsolatedRecoveryProfile`
|
|
875
|
+
);
|
|
873
876
|
const gateway =
|
|
874
877
|
provider === 'gateway'
|
|
875
878
|
? normalizeGatewayBuildOptions(providerSettings, 'settings.providerSettings.gateway', cwd)
|
|
@@ -878,14 +881,11 @@ function runtimeProviderSettings(
|
|
|
878
881
|
levelOverrides,
|
|
879
882
|
...(gateway === undefined ? {} : { gateway }),
|
|
880
883
|
...(webSearch === undefined ? {} : { webSearch }),
|
|
884
|
+
...(trustIsolatedRecoveryProfile === undefined ? {} : { trustIsolatedRecoveryProfile }),
|
|
881
885
|
};
|
|
882
886
|
return defaultLevel === undefined ? base : { ...base, defaultLevel };
|
|
883
887
|
}
|
|
884
888
|
|
|
885
|
-
function runtimeHelpCommand(provider: ProviderId): CommandParts {
|
|
886
|
-
return resolveProviderCommand(provider);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
889
|
function probeGatewayProvider(
|
|
890
890
|
adapter: ProviderAdapter,
|
|
891
891
|
runtimeSettings?: Record<string, unknown>
|
|
@@ -468,6 +468,8 @@ export interface BuildProviderCommandOptions {
|
|
|
468
468
|
readonly mcpConfig?: readonly string[];
|
|
469
469
|
/** Internal profile for provider-neutral structured-output correction turns. */
|
|
470
470
|
readonly structuredOutputRecovery?: boolean;
|
|
471
|
+
/** Preserve a caller-isolated CODEX_HOME profile during Codex recovery. */
|
|
472
|
+
readonly trustIsolatedCodexProfile?: boolean;
|
|
471
473
|
}
|
|
472
474
|
|
|
473
475
|
export interface TextEvent {
|
package/src/preflight.js
CHANGED
|
@@ -468,6 +468,31 @@ function validateProviderIsolationCapabilities(providerName, options) {
|
|
|
468
468
|
return errors;
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
function validateProviderExecutionSettings(providerName, settings, options) {
|
|
472
|
+
const metadata = getProviderMetadata(providerName);
|
|
473
|
+
if (!metadata.settingsValidator) return [];
|
|
474
|
+
|
|
475
|
+
const providerSettings = settings.providerSettings?.[providerName];
|
|
476
|
+
if (
|
|
477
|
+
!providerSettings ||
|
|
478
|
+
typeof providerSettings !== 'object' ||
|
|
479
|
+
Array.isArray(providerSettings)
|
|
480
|
+
) {
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const executionContext = options.requireDocker ? 'docker' : 'detached';
|
|
485
|
+
const error = metadata.settingsValidator(providerSettings, { executionContext });
|
|
486
|
+
if (!error) return [];
|
|
487
|
+
|
|
488
|
+
return [
|
|
489
|
+
formatError(`${metadata.displayName} configuration cannot run cluster agents`, error, [
|
|
490
|
+
metadata.authInstructions,
|
|
491
|
+
'Or select a different provider with: zeroshot providers set-default <provider>',
|
|
492
|
+
]),
|
|
493
|
+
];
|
|
494
|
+
}
|
|
495
|
+
|
|
471
496
|
function validateProvider(providerName, options) {
|
|
472
497
|
let metadata;
|
|
473
498
|
try {
|
|
@@ -623,7 +648,7 @@ async function runPreflight(options = {}) {
|
|
|
623
648
|
const errors = [];
|
|
624
649
|
const warnings = [];
|
|
625
650
|
|
|
626
|
-
const settings = loadSettings();
|
|
651
|
+
const settings = options.settings || loadSettings();
|
|
627
652
|
|
|
628
653
|
if (process.platform === 'win32') {
|
|
629
654
|
return {
|
|
@@ -648,6 +673,7 @@ async function runPreflight(options = {}) {
|
|
|
648
673
|
|
|
649
674
|
const providerResult = validateProvider(providerName, options);
|
|
650
675
|
errors.push(...providerResult.errors);
|
|
676
|
+
errors.push(...validateProviderExecutionSettings(providerName, settings, options));
|
|
651
677
|
warnings.push(...providerResult.warnings);
|
|
652
678
|
|
|
653
679
|
// 4. Check issue provider CLI (if required)
|
package/src/status-footer.js
CHANGED
|
@@ -151,21 +151,29 @@ class StatusFooter {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
* Print
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* MUST be used instead of console.log() when status footer is active.
|
|
159
|
-
* @param {string} text - Text to print (newline will be added)
|
|
154
|
+
* Print one logical line while coordinating with footer rendering.
|
|
155
|
+
* The caller owns line normalization; this method appends one newline.
|
|
156
|
+
* @param {string} text
|
|
160
157
|
*/
|
|
161
158
|
print(text) {
|
|
159
|
+
this._queueOrWrite(`${text}\n`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Write an exact streaming chunk while coordinating with footer rendering.
|
|
164
|
+
* @param {string} text
|
|
165
|
+
*/
|
|
166
|
+
write(text) {
|
|
167
|
+
this._queueOrWrite(String(text));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @private */
|
|
171
|
+
_queueOrWrite(text) {
|
|
162
172
|
if (this.isRendering) {
|
|
163
|
-
// Queue for later - render() will flush after restoring cursor
|
|
164
173
|
this.printQueue.push(text);
|
|
165
|
-
|
|
166
|
-
// Write immediately - no render in progress
|
|
167
|
-
process.stdout.write(text + '\n');
|
|
174
|
+
return;
|
|
168
175
|
}
|
|
176
|
+
process.stdout.write(text);
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
/**
|
|
@@ -176,8 +184,7 @@ class StatusFooter {
|
|
|
176
184
|
_flushPrintQueue() {
|
|
177
185
|
if (this.printQueue.length === 0) return;
|
|
178
186
|
|
|
179
|
-
|
|
180
|
-
const output = this.printQueue.map((text) => text + '\n').join('');
|
|
187
|
+
const output = this.printQueue.join('');
|
|
181
188
|
this.printQueue = [];
|
|
182
189
|
process.stdout.write(output);
|
|
183
190
|
}
|
|
@@ -1,97 +1,109 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { resolveEffectiveTaskStatus } from '../effective-status.js';
|
|
2
3
|
import { loadTasks } from '../store.js';
|
|
3
|
-
import { isProcessRunning } from '../runner.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const DEFAULT_LIMIT = 20;
|
|
6
|
+
|
|
7
|
+
function selectTasks(options = {}, deps = {}) {
|
|
8
|
+
const readTasks = deps.loadTasks || loadTasks;
|
|
9
|
+
const resolveStatus = deps.resolveEffectiveTaskStatus || resolveEffectiveTaskStatus;
|
|
10
|
+
const allTasks = Object.values(readTasks());
|
|
11
|
+
const selected = allTasks
|
|
12
|
+
.map((task) => ({ task, effectiveStatus: resolveStatus(task) }))
|
|
13
|
+
.sort((left, right) => new Date(left.task.createdAt) - new Date(right.task.createdAt))
|
|
14
|
+
.filter(({ effectiveStatus }) => !options.status || effectiveStatus.status === options.status)
|
|
15
|
+
.slice(0, options.limit || DEFAULT_LIMIT);
|
|
16
|
+
return { total: allTasks.length, selected };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function projectTask({ task, effectiveStatus }) {
|
|
20
|
+
return {
|
|
21
|
+
id: task.id,
|
|
22
|
+
status: effectiveStatus.status,
|
|
23
|
+
statusReason: effectiveStatus.reason,
|
|
24
|
+
cwd: task.cwd,
|
|
25
|
+
provider: task.provider || null,
|
|
26
|
+
model: task.model || null,
|
|
27
|
+
createdAt: task.createdAt,
|
|
28
|
+
updatedAt: task.updatedAt,
|
|
29
|
+
exitCode: task.exitCode ?? null,
|
|
30
|
+
error: task.error || null,
|
|
31
|
+
attachable: task.attachable === true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getTasksData(options = {}, deps = {}) {
|
|
36
|
+
return selectTasks(options, deps).selected.map(projectTask);
|
|
37
|
+
}
|
|
8
38
|
|
|
9
|
-
|
|
39
|
+
export function listTasks(options = {}, deps = {}) {
|
|
40
|
+
const { selected, total } = selectTasks(options, deps);
|
|
41
|
+
|
|
42
|
+
if (total === 0) {
|
|
10
43
|
console.log(chalk.dim('No tasks found.'));
|
|
11
44
|
return;
|
|
12
45
|
}
|
|
13
46
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let filtered = taskList;
|
|
19
|
-
if (options.status) {
|
|
20
|
-
filtered = taskList.filter((t) => t.status === options.status);
|
|
47
|
+
if (options.verbose) {
|
|
48
|
+
printVerboseTasks(selected, total);
|
|
49
|
+
} else {
|
|
50
|
+
printTaskTable(selected, total);
|
|
21
51
|
}
|
|
52
|
+
}
|
|
22
53
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const statusColor =
|
|
40
|
-
{
|
|
41
|
-
running: chalk.green,
|
|
42
|
-
completed: chalk.green,
|
|
43
|
-
failed: chalk.red,
|
|
44
|
-
stale: chalk.yellow,
|
|
45
|
-
}[status] || chalk.dim;
|
|
46
|
-
|
|
47
|
-
const age = getAge(task.createdAt);
|
|
48
|
-
const timestamp = new Date(task.createdAt).toLocaleString();
|
|
49
|
-
|
|
50
|
-
console.log(
|
|
51
|
-
`${statusColor('●')} ${chalk.cyan(task.id)} ${statusColor(`[${status}]`)} ${chalk.dim(age + ' • ' + timestamp)}`
|
|
52
|
-
);
|
|
53
|
-
console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
|
|
54
|
-
console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
|
|
55
|
-
if (task.pid && status === 'running') {
|
|
56
|
-
console.log(` ${chalk.dim('PID:')} ${task.pid}`);
|
|
57
|
-
}
|
|
58
|
-
if (task.error) {
|
|
59
|
-
console.log(` ${chalk.red('Error:')} ${task.error}`);
|
|
60
|
-
}
|
|
61
|
-
console.log();
|
|
54
|
+
function printVerboseTasks(selected, total) {
|
|
55
|
+
console.log(chalk.bold(`\nTasks (${selected.length}/${total})\n`));
|
|
56
|
+
|
|
57
|
+
for (const { task, effectiveStatus } of selected) {
|
|
58
|
+
const statusColor = colorForStatus(effectiveStatus.status);
|
|
59
|
+
const age = getAge(task.createdAt);
|
|
60
|
+
const timestamp = new Date(task.createdAt).toLocaleString();
|
|
61
|
+
|
|
62
|
+
const heading = `${statusColor('●')} ${chalk.cyan(task.id)}`;
|
|
63
|
+
const status = statusColor(`[${effectiveStatus.status}]`);
|
|
64
|
+
const timing = chalk.dim(age + ' • ' + timestamp);
|
|
65
|
+
console.log(`${heading} ${status} ${timing}`);
|
|
66
|
+
console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
|
|
67
|
+
console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
|
|
68
|
+
if (task.pid && effectiveStatus.status === 'running') {
|
|
69
|
+
console.log(` ${chalk.dim('PID:')} ${task.pid}`);
|
|
62
70
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
console.log(chalk.bold(`\n=== Tasks (${filtered.length}/${taskList.length}) ===`));
|
|
66
|
-
console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
|
|
67
|
-
console.log('-'.repeat(100));
|
|
68
|
-
|
|
69
|
-
for (const task of filtered) {
|
|
70
|
-
// Verify running status
|
|
71
|
-
let status = task.status;
|
|
72
|
-
if (status === 'running' && !isProcessRunning(task.pid)) {
|
|
73
|
-
status = 'stale';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const statusColor =
|
|
77
|
-
{
|
|
78
|
-
running: chalk.green,
|
|
79
|
-
completed: chalk.green,
|
|
80
|
-
failed: chalk.red,
|
|
81
|
-
stale: chalk.yellow,
|
|
82
|
-
}[status] || chalk.dim;
|
|
83
|
-
|
|
84
|
-
const age = getAge(task.createdAt);
|
|
85
|
-
const cwd = task.cwd.replace(process.env.HOME, '~');
|
|
86
|
-
|
|
87
|
-
console.log(
|
|
88
|
-
`${chalk.cyan(task.id.padEnd(25))} ${statusColor(status.padEnd(12))} ${chalk.dim(age.padEnd(10))} ${chalk.dim(cwd)}`
|
|
89
|
-
);
|
|
71
|
+
if (task.error) {
|
|
72
|
+
console.log(` ${chalk.red('Error:')} ${task.error}`);
|
|
90
73
|
}
|
|
91
74
|
console.log();
|
|
92
75
|
}
|
|
93
76
|
}
|
|
94
77
|
|
|
78
|
+
function printTaskTable(selected, total) {
|
|
79
|
+
console.log(chalk.bold(`\n=== Tasks (${selected.length}/${total}) ===`));
|
|
80
|
+
console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
|
|
81
|
+
console.log('-'.repeat(100));
|
|
82
|
+
|
|
83
|
+
for (const { task, effectiveStatus } of selected) {
|
|
84
|
+
const statusColor = colorForStatus(effectiveStatus.status);
|
|
85
|
+
const age = getAge(task.createdAt);
|
|
86
|
+
const cwd = process.env.HOME ? task.cwd.replace(process.env.HOME, '~') : task.cwd;
|
|
87
|
+
|
|
88
|
+
const id = chalk.cyan(task.id.padEnd(25));
|
|
89
|
+
const status = statusColor(effectiveStatus.status.padEnd(12));
|
|
90
|
+
const timing = chalk.dim(age.padEnd(10));
|
|
91
|
+
console.log(`${id} ${status} ${timing} ${chalk.dim(cwd)}`);
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function colorForStatus(status) {
|
|
97
|
+
return (
|
|
98
|
+
{
|
|
99
|
+
running: chalk.green,
|
|
100
|
+
completed: chalk.green,
|
|
101
|
+
failed: chalk.red,
|
|
102
|
+
stale: chalk.yellow,
|
|
103
|
+
}[status] || chalk.dim
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
function getAge(dateStr) {
|
|
96
108
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
97
109
|
const mins = Math.floor(diff / 60000);
|