@fitlab-ai/agent-infra 0.8.2 → 0.8.4
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.zh-CN.md +10 -0
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +2 -2
- package/dist/lib/cp.js +57 -16
- package/dist/lib/decide.js +2 -2
- package/dist/lib/defaults.json +2 -0
- package/dist/lib/init.js +7 -7
- package/dist/lib/merge.js +1 -1
- package/dist/lib/prompt.js +1 -1
- package/dist/lib/run/index.js +7 -7
- package/dist/lib/run/prompt.js +1 -1
- package/dist/lib/sandbox/capture.js +10 -8
- package/dist/lib/sandbox/clipboard/bridge.js +59 -21
- package/dist/lib/sandbox/clipboard/inbox.js +82 -0
- package/dist/lib/sandbox/clipboard/index.js +5 -6
- package/dist/lib/sandbox/clipboard/linux.js +91 -0
- package/dist/lib/sandbox/clipboard/paths.js +1 -1
- package/dist/lib/sandbox/clipboard/win32.js +144 -0
- package/dist/lib/sandbox/commands/create.js +106 -88
- package/dist/lib/sandbox/commands/enter.js +9 -9
- package/dist/lib/sandbox/commands/list-running.js +2 -2
- package/dist/lib/sandbox/commands/ls.js +7 -7
- package/dist/lib/sandbox/commands/prune.js +8 -8
- package/dist/lib/sandbox/commands/rebuild.js +38 -50
- package/dist/lib/sandbox/commands/refresh.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +11 -11
- package/dist/lib/sandbox/commands/show.js +4 -4
- package/dist/lib/sandbox/commands/start.js +4 -4
- package/dist/lib/sandbox/commands/vm.js +4 -4
- package/dist/lib/sandbox/config.js +10 -4
- package/dist/lib/sandbox/constants.js +4 -1
- package/dist/lib/sandbox/credentials.js +1 -1
- package/dist/lib/sandbox/dotfiles.js +1 -1
- package/dist/lib/sandbox/engine.js +3 -3
- package/dist/lib/sandbox/engines/index.js +5 -5
- package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
- package/dist/lib/sandbox/image-build.js +80 -0
- package/dist/lib/sandbox/image-prune.js +2 -2
- package/dist/lib/sandbox/index.js +10 -10
- package/dist/lib/sandbox/managed-fs.js +1 -1
- package/dist/lib/sandbox/readme-scaffold.js +1 -1
- package/dist/lib/sandbox/runtimes/base.dockerfile +3 -0
- package/dist/lib/sandbox/shell.js +9 -1
- package/dist/lib/sandbox/tools.js +4 -4
- package/dist/lib/server/adapters/feishu/index.js +10 -3
- package/dist/lib/server/adapters/feishu/renderer.js +88 -0
- package/dist/lib/server/adapters/feishu/transport.js +2 -13
- package/dist/lib/server/daemon.js +64 -43
- package/dist/lib/server/display.js +83 -0
- package/dist/lib/server/index.js +2 -2
- package/dist/lib/server/process-control.js +2 -2
- package/dist/lib/server/protocol.js +2 -2
- package/dist/lib/server/streamer.js +5 -4
- package/dist/lib/task/commands/cat.js +2 -2
- package/dist/lib/task/commands/decisions.js +4 -4
- package/dist/lib/task/commands/files.js +3 -3
- package/dist/lib/task/commands/grep.js +3 -3
- package/dist/lib/task/commands/issue-body.js +4 -4
- package/dist/lib/task/commands/log.js +5 -5
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/commands/show.js +1 -1
- package/dist/lib/task/commands/status.js +71 -25
- package/dist/lib/task/index.js +9 -9
- package/dist/lib/task/resolve-ref.js +1 -1
- package/dist/lib/task/workflow-warnings.js +94 -0
- package/dist/lib/update.js +4 -4
- package/lib/cp.ts +56 -15
- package/lib/defaults.json +2 -0
- package/lib/sandbox/capture.ts +5 -3
- package/lib/sandbox/clipboard/bridge.ts +62 -18
- package/lib/sandbox/clipboard/inbox.ts +83 -0
- package/lib/sandbox/clipboard/index.ts +5 -6
- package/lib/sandbox/clipboard/linux.ts +124 -0
- package/lib/sandbox/clipboard/win32.ts +173 -0
- package/lib/sandbox/commands/create.ts +132 -88
- package/lib/sandbox/commands/rebuild.ts +42 -54
- package/lib/sandbox/config.ts +13 -1
- package/lib/sandbox/constants.ts +4 -0
- package/lib/sandbox/image-build.ts +134 -0
- package/lib/sandbox/runtimes/base.dockerfile +3 -0
- package/lib/sandbox/shell.ts +11 -2
- package/lib/sandbox/tools.ts +5 -5
- package/lib/server/adapters/_contract.ts +3 -0
- package/lib/server/adapters/feishu/index.ts +11 -3
- package/lib/server/adapters/feishu/renderer.ts +99 -0
- package/lib/server/adapters/feishu/transport.ts +5 -18
- package/lib/server/daemon.ts +74 -38
- package/lib/server/display.ts +136 -0
- package/lib/server/streamer.ts +5 -4
- package/lib/task/commands/log.ts +3 -3
- package/lib/task/commands/status.ts +102 -21
- package/lib/task/workflow-warnings.ts +121 -0
- package/package.json +2 -2
- package/templates/.agents/README.en.md +3 -0
- package/templates/.agents/README.zh-CN.md +3 -0
- package/templates/.agents/rules/create-issue.github.en.md +4 -4
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -4
- package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
- package/templates/.agents/rules/issue-sync.github.en.md +13 -0
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
- package/templates/.agents/rules/next-step-output.en.md +15 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
- package/templates/.agents/rules/pr-sync.github.en.md +17 -2
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
- package/templates/.agents/rules/review-handshake.en.md +2 -2
- package/templates/.agents/rules/review-handshake.zh-CN.md +2 -2
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/rules/testing-discipline.en.md +3 -41
- package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +213 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +6 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +142 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +142 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +6 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +165 -41
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +36 -6
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -1
- package/templates/.agents/templates/task.en.md +7 -0
- package/templates/.agents/templates/task.zh-CN.md +7 -0
- package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
- package/templates/.github/workflows/metadata-sync.yml +5 -0
- package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
package/lib/server/daemon.ts
CHANGED
|
@@ -1,87 +1,123 @@
|
|
|
1
1
|
import { VERSION } from '../version.ts';
|
|
2
2
|
import { loadServerConfig } from './config.ts';
|
|
3
|
+
import type { ServerConfig } from './config.ts';
|
|
3
4
|
import { createLogger } from './logger.ts';
|
|
5
|
+
import type { Logger } from './logger.ts';
|
|
4
6
|
import { loadAdapters, unloadAdapters } from './plugin-loader.ts';
|
|
5
7
|
import type { InboundMessage } from './adapters/_contract.ts';
|
|
6
8
|
import { authorize } from './auth.ts';
|
|
7
9
|
import { commandHelp, parseCommand } from './protocol.ts';
|
|
8
10
|
import { runAi } from './runner.ts';
|
|
11
|
+
import type { RunnerOptions, RunnerResult } from './runner.ts';
|
|
9
12
|
import { streamCommand } from './streamer.ts';
|
|
13
|
+
import { markdownMessage, replyOutbound, textMessage } from './display.ts';
|
|
14
|
+
import { buildStatusModel, statusModelToDisplay, type StatusModel } from '../task/commands/status.ts';
|
|
10
15
|
|
|
11
16
|
function errorMessage(error: unknown): string {
|
|
12
17
|
return error instanceof Error ? error.message : String(error);
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// - runDaemon() awaits a shutdown promise that only resolves once a
|
|
23
|
-
// SIGINT/SIGTERM handler has finished graceful cleanup. We never unref()
|
|
24
|
-
// the only keep-alive timer (that would let the process exit immediately).
|
|
25
|
-
export async function runDaemon(): Promise<void> {
|
|
26
|
-
let config;
|
|
27
|
-
try {
|
|
28
|
-
config = loadServerConfig();
|
|
29
|
-
} catch (error) {
|
|
30
|
-
process.stderr.write(`${errorMessage(error)}\n`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
20
|
+
type MessageDispatcherOptions = {
|
|
21
|
+
config: ServerConfig;
|
|
22
|
+
logger: Pick<Logger, 'info'>;
|
|
23
|
+
runAi?: (args: string[], options?: RunnerOptions) => Promise<RunnerResult>;
|
|
24
|
+
buildStatusModel?: (ref: string) => StatusModel;
|
|
25
|
+
statusModelToDisplay?: (model: StatusModel) => ReturnType<typeof statusModelToDisplay>;
|
|
26
|
+
};
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
function isTaskStatus(argv: string[]): boolean {
|
|
29
|
+
return argv[0] === 'task' && argv[1] === 'status' && typeof argv[2] === 'string';
|
|
30
|
+
}
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
});
|
|
32
|
+
export function createMessageDispatcher(options: MessageDispatcherOptions): (message: InboundMessage) => Promise<void> {
|
|
33
|
+
const runAiImpl = options.runAi ?? runAi;
|
|
34
|
+
const buildStatusModelImpl = options.buildStatusModel ?? buildStatusModel;
|
|
35
|
+
const statusModelToDisplayImpl = options.statusModelToDisplay ?? statusModelToDisplay;
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
return async (message: InboundMessage): Promise<void> => {
|
|
44
38
|
const plan = parseCommand(message.text);
|
|
45
39
|
if (plan.kind === 'ignore') return;
|
|
46
40
|
if (plan.kind === 'error') {
|
|
47
|
-
await message
|
|
48
|
-
logger.info(`command rejected from ${message.adapter}:${message.userId}: ${plan.message}`);
|
|
41
|
+
await replyOutbound(message, textMessage(plan.message));
|
|
42
|
+
options.logger.info(`command rejected from ${message.adapter}:${message.userId}: ${plan.message}`);
|
|
49
43
|
return;
|
|
50
44
|
}
|
|
51
45
|
if (plan.kind === 'builtin' && plan.name === 'ping') {
|
|
52
|
-
await message
|
|
46
|
+
await replyOutbound(message, textMessage(`pong ${VERSION}`));
|
|
53
47
|
return;
|
|
54
48
|
}
|
|
55
49
|
if (plan.kind === 'builtin' && plan.name === 'help') {
|
|
56
|
-
await message
|
|
50
|
+
await replyOutbound(message, markdownMessage(commandHelp()));
|
|
57
51
|
return;
|
|
58
52
|
}
|
|
59
53
|
if (plan.kind === 'builtin' && plan.name === 'version') {
|
|
60
|
-
await message
|
|
54
|
+
await replyOutbound(message, textMessage(`agent-infra ${VERSION}`));
|
|
61
55
|
return;
|
|
62
56
|
}
|
|
63
57
|
if (plan.kind === 'ai') {
|
|
64
58
|
const allowed = authorize(
|
|
65
59
|
{ adapter: message.adapter, userId: message.userId },
|
|
66
60
|
plan.role,
|
|
67
|
-
config.auth
|
|
61
|
+
options.config.auth
|
|
68
62
|
);
|
|
69
63
|
if (!allowed.ok) {
|
|
70
|
-
await message
|
|
71
|
-
logger.info(`unauthorized command from ${message.adapter}:${message.userId}: ${allowed.message}`);
|
|
64
|
+
await replyOutbound(message, textMessage(allowed.message));
|
|
65
|
+
options.logger.info(`unauthorized command from ${message.adapter}:${message.userId}: ${allowed.message}`);
|
|
72
66
|
return;
|
|
73
67
|
}
|
|
68
|
+
|
|
69
|
+
if (isTaskStatus(plan.argv)) {
|
|
70
|
+
try {
|
|
71
|
+
await replyOutbound(message, statusModelToDisplayImpl(buildStatusModelImpl(plan.argv[2]!)));
|
|
72
|
+
return;
|
|
73
|
+
} catch {
|
|
74
|
+
// Fall back to the existing CLI streaming path. This preserves the
|
|
75
|
+
// old behavior for invalid refs and any status-model collection error.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
await streamCommand(
|
|
75
80
|
{
|
|
76
81
|
title: `ai ${plan.argv.join(' ')}`,
|
|
77
|
-
chunkChars: typeof config.stream?.chunkChars === 'number' ? config.stream.chunkChars : 4000,
|
|
78
|
-
throttleMs: typeof config.stream?.throttleMs === 'number' ? config.stream.throttleMs : 1500
|
|
82
|
+
chunkChars: typeof options.config.stream?.chunkChars === 'number' ? options.config.stream.chunkChars : 4000,
|
|
83
|
+
throttleMs: typeof options.config.stream?.throttleMs === 'number' ? options.config.stream.throttleMs : 1500
|
|
79
84
|
},
|
|
80
|
-
(emit) =>
|
|
81
|
-
(
|
|
85
|
+
(emit) => runAiImpl(plan.argv, { onChunk: emit }),
|
|
86
|
+
(outbound) => replyOutbound(message, outbound)
|
|
82
87
|
);
|
|
83
88
|
}
|
|
84
89
|
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The daemon main loop. Runs in the detached child spawned by
|
|
93
|
+
// process-control.start(), or in the foreground for debugging.
|
|
94
|
+
//
|
|
95
|
+
// Lifecycle (keep-alive / shutdown model):
|
|
96
|
+
// - The heartbeat interval is kept *ref'd*. It is both the keep-alive that
|
|
97
|
+
// holds the event loop open while subtask A has no adapters, and the
|
|
98
|
+
// observable signal that `ai server logs -f` shows.
|
|
99
|
+
// - runDaemon() awaits a shutdown promise that only resolves once a
|
|
100
|
+
// SIGINT/SIGTERM handler has finished graceful cleanup. We never unref()
|
|
101
|
+
// the only keep-alive timer (that would let the process exit immediately).
|
|
102
|
+
export async function runDaemon(): Promise<void> {
|
|
103
|
+
let config;
|
|
104
|
+
try {
|
|
105
|
+
config = loadServerConfig();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
process.stderr.write(`${errorMessage(error)}\n`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const logger = createLogger(config.log);
|
|
112
|
+
logger.info(`daemon starting agent-infra ${VERSION} pid=${process.pid}`);
|
|
113
|
+
|
|
114
|
+
const abortController = new AbortController();
|
|
115
|
+
let resolveShutdown: () => void = () => {};
|
|
116
|
+
const shutdown = new Promise<void>((resolve) => {
|
|
117
|
+
resolveShutdown = resolve;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const dispatch = createMessageDispatcher({ config, logger });
|
|
85
121
|
|
|
86
122
|
const ctx = { config, logger, dispatch, signal: abortController.signal };
|
|
87
123
|
const adapters = await loadAdapters(config, ctx);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export type OutboundMessage = string | DisplayMessage;
|
|
2
|
+
|
|
3
|
+
export type DisplayTone = 'info' | 'success' | 'warning' | 'danger' | 'running';
|
|
4
|
+
|
|
5
|
+
export type DisplayMessage =
|
|
6
|
+
| { kind: 'text'; text: string }
|
|
7
|
+
| { kind: 'markdown'; markdown: string }
|
|
8
|
+
| { kind: 'table'; title?: string; columns: string[]; rows: string[][] }
|
|
9
|
+
| { kind: 'status-card'; title: string; tone: DisplayTone; fields?: [string, string][]; body?: string }
|
|
10
|
+
| {
|
|
11
|
+
kind: 'stream-event';
|
|
12
|
+
title: string;
|
|
13
|
+
phase: 'started' | 'chunk' | 'finished';
|
|
14
|
+
text?: string;
|
|
15
|
+
exitCode?: number | null;
|
|
16
|
+
signal?: NodeJS.Signals | null;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: 'command-result';
|
|
20
|
+
title: string;
|
|
21
|
+
exitCode: number | null;
|
|
22
|
+
signal: NodeJS.Signals | null;
|
|
23
|
+
stdout?: string;
|
|
24
|
+
stderr?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function textMessage(text: string): DisplayMessage {
|
|
28
|
+
return { kind: 'text', text };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function markdownMessage(markdown: string): DisplayMessage {
|
|
32
|
+
return { kind: 'markdown', markdown };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function tableMessage(columns: string[], rows: string[][], title?: string): DisplayMessage {
|
|
36
|
+
return { kind: 'table', title, columns, rows };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function statusCard(
|
|
40
|
+
title: string,
|
|
41
|
+
tone: DisplayTone,
|
|
42
|
+
fields?: [string, string][],
|
|
43
|
+
body?: string
|
|
44
|
+
): DisplayMessage {
|
|
45
|
+
return { kind: 'status-card', title, tone, fields, body };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function streamEvent(
|
|
49
|
+
title: string,
|
|
50
|
+
phase: 'started' | 'chunk' | 'finished',
|
|
51
|
+
text?: string,
|
|
52
|
+
exitCode?: number | null,
|
|
53
|
+
signal?: NodeJS.Signals | null
|
|
54
|
+
): DisplayMessage {
|
|
55
|
+
const message: Extract<DisplayMessage, { kind: 'stream-event' }> = { kind: 'stream-event', title, phase };
|
|
56
|
+
if (text !== undefined) message.text = text;
|
|
57
|
+
if (exitCode !== undefined) message.exitCode = exitCode;
|
|
58
|
+
if (signal !== undefined) message.signal = signal;
|
|
59
|
+
return message;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function commandResult(
|
|
63
|
+
title: string,
|
|
64
|
+
exitCode: number | null,
|
|
65
|
+
signal: NodeJS.Signals | null,
|
|
66
|
+
stdout?: string,
|
|
67
|
+
stderr?: string
|
|
68
|
+
): DisplayMessage {
|
|
69
|
+
return { kind: 'command-result', title, exitCode, signal, stdout, stderr };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function normalizeOutbound(message: OutboundMessage): DisplayMessage {
|
|
73
|
+
return typeof message === 'string' ? textMessage(message) : message;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function tableToText(message: Extract<DisplayMessage, { kind: 'table' }>): string {
|
|
77
|
+
const lines: string[] = [];
|
|
78
|
+
if (message.title) lines.push(message.title);
|
|
79
|
+
lines.push(message.columns.join(' | '));
|
|
80
|
+
lines.push(message.columns.map(() => '---').join(' | '));
|
|
81
|
+
for (const row of message.rows) {
|
|
82
|
+
lines.push(row.join(' | '));
|
|
83
|
+
}
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compact(lines: Array<string | undefined>): string {
|
|
88
|
+
return lines.filter((line): line is string => typeof line === 'string' && line !== '').join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unknownDisplayKind(message: never): string {
|
|
92
|
+
return `[unknown: ${String((message as { kind?: unknown }).kind)}]`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function outboundToText(message: OutboundMessage): string {
|
|
96
|
+
const normalized = normalizeOutbound(message);
|
|
97
|
+
switch (normalized.kind) {
|
|
98
|
+
case 'text':
|
|
99
|
+
return normalized.text;
|
|
100
|
+
case 'markdown':
|
|
101
|
+
return normalized.markdown;
|
|
102
|
+
case 'table':
|
|
103
|
+
return tableToText(normalized);
|
|
104
|
+
case 'status-card':
|
|
105
|
+
return compact([
|
|
106
|
+
normalized.title,
|
|
107
|
+
...(normalized.fields ?? []).map(([label, value]) => `${label}: ${value}`),
|
|
108
|
+
normalized.body
|
|
109
|
+
]);
|
|
110
|
+
case 'stream-event':
|
|
111
|
+
if (normalized.phase === 'started') return `started ${normalized.title}`;
|
|
112
|
+
if (normalized.phase === 'chunk') return normalized.text ?? '';
|
|
113
|
+
return `finished ${normalized.title} exitCode=${normalized.exitCode ?? 'null'} signal=${normalized.signal ?? 'null'}`;
|
|
114
|
+
case 'command-result':
|
|
115
|
+
return compact([
|
|
116
|
+
`finished ${normalized.title} exitCode=${normalized.exitCode ?? 'null'} signal=${normalized.signal ?? 'null'}`,
|
|
117
|
+
normalized.stdout,
|
|
118
|
+
normalized.stderr
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
return unknownDisplayKind(normalized);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function replyOutbound(
|
|
125
|
+
inbound: {
|
|
126
|
+
reply: (text: string) => Promise<void>;
|
|
127
|
+
replyDisplay?: (message: OutboundMessage) => Promise<void>;
|
|
128
|
+
},
|
|
129
|
+
message: OutboundMessage
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
if (inbound.replyDisplay) {
|
|
132
|
+
await inbound.replyDisplay(message);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await inbound.reply(outboundToText(message));
|
|
136
|
+
}
|
package/lib/server/streamer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { streamEvent, type OutboundMessage } from './display.ts';
|
|
1
2
|
import { redactSecrets } from './redact.ts';
|
|
2
3
|
import type { RunnerResult } from './runner.ts';
|
|
3
4
|
|
|
@@ -19,9 +20,9 @@ function chunks(text: string, size: number): string[] {
|
|
|
19
20
|
export async function streamCommand(
|
|
20
21
|
options: StreamOptions,
|
|
21
22
|
run: (emit?: (chunk: string) => Promise<void>) => Promise<RunnerResult>,
|
|
22
|
-
send: (
|
|
23
|
+
send: (message: OutboundMessage) => Promise<void>
|
|
23
24
|
): Promise<RunnerResult> {
|
|
24
|
-
await send(
|
|
25
|
+
await send(streamEvent(options.title, 'started'));
|
|
25
26
|
const size = options.chunkChars ?? 4000;
|
|
26
27
|
const throttleMs = options.throttleMs ?? 0;
|
|
27
28
|
let streamed = false;
|
|
@@ -34,7 +35,7 @@ export async function streamCommand(
|
|
|
34
35
|
buffer = '';
|
|
35
36
|
lastFlush = Date.now();
|
|
36
37
|
for (const chunk of chunks(text, size)) {
|
|
37
|
-
await send(chunk);
|
|
38
|
+
await send(streamEvent(options.title, 'chunk', chunk));
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
41
|
|
|
@@ -52,6 +53,6 @@ export async function streamCommand(
|
|
|
52
53
|
buffer += [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
53
54
|
}
|
|
54
55
|
await flush();
|
|
55
|
-
await send(
|
|
56
|
+
await send(streamEvent(options.title, 'finished', undefined, result.exitCode, result.signal));
|
|
56
57
|
return result;
|
|
57
58
|
}
|
package/lib/task/commands/log.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { formatTable } from '../../table.ts';
|
|
3
3
|
import { resolveTaskRef } from '../resolve-ref.ts';
|
|
4
|
-
import { parseLedger,
|
|
4
|
+
import { parseLedger, type LedgerRow } from '../ledger.ts';
|
|
5
5
|
|
|
6
6
|
const USAGE = `Usage: ai task log <N | #N | TASK-id>
|
|
7
7
|
|
|
@@ -14,7 +14,7 @@ Columns: # (row) / STEP / AGENT / STARTED / DONE / NOTE
|
|
|
14
14
|
A human-executed step shows AGENT as 'human' and, when it has no start marker,
|
|
15
15
|
a '-' STARTED placeholder. Review-step NOTE also carries two human counts in
|
|
16
16
|
the verdict list, right after blockers/major/minor: manual-validation
|
|
17
|
-
and human-decision (current ledger stage total).
|
|
17
|
+
and human-decision (current pending ledger stage total).
|
|
18
18
|
`;
|
|
19
19
|
|
|
20
20
|
const TABLE_HEADERS = ['#', 'STEP', 'AGENT', 'STARTED', 'DONE', 'NOTE'] as const;
|
|
@@ -108,7 +108,7 @@ function pairEntries(entries: LogEntry[]): StepRow[] {
|
|
|
108
108
|
function countHumanDecisionsByStage(rows: LedgerRow[]): Map<ReviewStage, number> {
|
|
109
109
|
const counts = new Map<ReviewStage, number>();
|
|
110
110
|
for (const row of rows) {
|
|
111
|
-
if (!isReviewStage(row.stage) ||
|
|
111
|
+
if (!isReviewStage(row.stage) || row.status !== 'needs-human-decision') continue;
|
|
112
112
|
counts.set(row.stage, (counts.get(row.stage) ?? 0) + 1);
|
|
113
113
|
}
|
|
114
114
|
return counts;
|
|
@@ -6,7 +6,9 @@ import { resolveTaskRef } from '../resolve-ref.ts';
|
|
|
6
6
|
import { enumerateArtifacts, type Artifact } from '../artifacts.ts';
|
|
7
7
|
import { parseTaskFrontmatter, extractTitle, type Frontmatter } from '../frontmatter.ts';
|
|
8
8
|
import { loadShortIdByTaskId } from '../short-id.ts';
|
|
9
|
+
import { getOpenWorkflowWarnings, formatWorkflowWarningSummary, type WorkflowWarning } from '../workflow-warnings.ts';
|
|
9
10
|
import { parseActivityLog, pairEntries } from './log.ts';
|
|
11
|
+
import { statusCard, type DisplayMessage } from '../../server/display.ts';
|
|
10
12
|
|
|
11
13
|
const USAGE = `Usage: ai task status <N | #N | TASK-id>
|
|
12
14
|
|
|
@@ -322,12 +324,28 @@ type StatusModel = {
|
|
|
322
324
|
shortId: string;
|
|
323
325
|
title: string;
|
|
324
326
|
metadata: [string, string][];
|
|
327
|
+
workflowWarnings: WorkflowWarning[];
|
|
325
328
|
artifacts: { count: number; groups: { stage: string; files: string[] }[] };
|
|
326
329
|
workflow: WorkflowInfo;
|
|
327
330
|
runtime: RuntimeInfo;
|
|
328
331
|
git: GitInfo;
|
|
329
332
|
};
|
|
330
333
|
|
|
334
|
+
type BuildStatusModelInput = {
|
|
335
|
+
taskId: string;
|
|
336
|
+
taskDir: string;
|
|
337
|
+
taskMdPath: string;
|
|
338
|
+
repoRoot: string;
|
|
339
|
+
shortId?: string;
|
|
340
|
+
run?: Runner;
|
|
341
|
+
now?: Date;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
type BuildStatusModelOptions = {
|
|
345
|
+
run?: Runner;
|
|
346
|
+
now?: Date;
|
|
347
|
+
};
|
|
348
|
+
|
|
331
349
|
// Indent each label/value pair by two spaces and pad labels to a common width so
|
|
332
350
|
// every section reads as an aligned "key value" block.
|
|
333
351
|
function renderPairs(rows: [string, string][]): string[] {
|
|
@@ -343,6 +361,14 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
343
361
|
|
|
344
362
|
lines.push('', 'Metadata', ...renderPairs(model.metadata));
|
|
345
363
|
|
|
364
|
+
if (model.workflowWarnings.length > 0) {
|
|
365
|
+
lines.push(
|
|
366
|
+
'',
|
|
367
|
+
`Workflow Warnings (${model.workflowWarnings.length} open)`,
|
|
368
|
+
...formatWorkflowWarningSummary(model.workflowWarnings).map((line) => ` ${line}`)
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
346
372
|
lines.push('', `Artifacts (${model.artifacts.count})`);
|
|
347
373
|
if (model.artifacts.groups.length === 0) {
|
|
348
374
|
lines.push(' (none)');
|
|
@@ -394,36 +420,89 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
394
420
|
return lines;
|
|
395
421
|
}
|
|
396
422
|
|
|
397
|
-
function
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const resolved = resolveTaskRef(args[0]!);
|
|
405
|
-
if (!resolved.ok) {
|
|
406
|
-
process.stderr.write(`ai task status: ${resolved.message}\n`);
|
|
407
|
-
process.exitCode = 1;
|
|
408
|
-
return;
|
|
423
|
+
function modelTone(model: StatusModel): 'info' | 'success' | 'warning' | 'danger' | 'running' {
|
|
424
|
+
if (model.workflow.state === 'in-progress') return model.workflow.stale === 'yes' ? 'warning' : 'running';
|
|
425
|
+
if (model.metadata.some(([key, value]) => key === 'status' && value === 'completed')) return 'success';
|
|
426
|
+
if (model.metadata.some(([key, value]) => key === 'status' && (value === 'blocked' || value === 'cancelled'))) {
|
|
427
|
+
return 'danger';
|
|
409
428
|
}
|
|
429
|
+
return 'info';
|
|
430
|
+
}
|
|
410
431
|
|
|
411
|
-
|
|
432
|
+
function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
433
|
+
const content = fs.readFileSync(input.taskMdPath, 'utf8');
|
|
412
434
|
const fm = parseTaskFrontmatter(content);
|
|
413
|
-
const run = makeRunner(
|
|
414
|
-
const artifacts = enumerateArtifacts(
|
|
415
|
-
const workflow = collectWorkflow(content);
|
|
435
|
+
const run = input.run ?? makeRunner(input.repoRoot);
|
|
436
|
+
const artifacts = enumerateArtifacts(input.taskDir);
|
|
437
|
+
const workflow = collectWorkflow(content, input.now);
|
|
416
438
|
|
|
417
|
-
|
|
418
|
-
taskId:
|
|
419
|
-
shortId: loadShortIdByTaskId(
|
|
439
|
+
return {
|
|
440
|
+
taskId: input.taskId,
|
|
441
|
+
shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
|
|
420
442
|
title: extractTitle(content),
|
|
421
443
|
metadata: collectMetadata(fm),
|
|
444
|
+
workflowWarnings: getOpenWorkflowWarnings(content),
|
|
422
445
|
artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
|
|
423
446
|
workflow,
|
|
424
|
-
runtime: collectRuntime(
|
|
447
|
+
runtime: collectRuntime(input.taskDir, workflow, run),
|
|
425
448
|
git: collectGit(fm.branch ?? '', run)
|
|
426
449
|
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function buildStatusModel(ref: string, options?: BuildStatusModelOptions): StatusModel;
|
|
453
|
+
function buildStatusModel(input: BuildStatusModelInput): StatusModel;
|
|
454
|
+
function buildStatusModel(
|
|
455
|
+
refOrInput: string | BuildStatusModelInput,
|
|
456
|
+
options: BuildStatusModelOptions = {}
|
|
457
|
+
): StatusModel {
|
|
458
|
+
if (typeof refOrInput !== 'string') {
|
|
459
|
+
return buildFromResolved(refOrInput);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const resolved = resolveTaskRef(refOrInput);
|
|
463
|
+
if (!resolved.ok) {
|
|
464
|
+
throw new Error(resolved.message);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return buildFromResolved({
|
|
468
|
+
taskId: resolved.taskId,
|
|
469
|
+
taskDir: resolved.taskDir,
|
|
470
|
+
taskMdPath: resolved.taskMdPath,
|
|
471
|
+
repoRoot: resolved.repoRoot,
|
|
472
|
+
run: options.run,
|
|
473
|
+
now: options.now
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function statusModelToDisplay(model: StatusModel): DisplayMessage {
|
|
478
|
+
return statusCard(
|
|
479
|
+
`Task ${model.taskId} (${model.shortId})`,
|
|
480
|
+
modelTone(model),
|
|
481
|
+
[
|
|
482
|
+
['workflow', model.workflow.state],
|
|
483
|
+
['step', model.workflow.step],
|
|
484
|
+
['runtime', model.runtime.status],
|
|
485
|
+
['git', model.git.uncommitted]
|
|
486
|
+
],
|
|
487
|
+
renderStatus(model).join('\n')
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function status(args: string[] = []): void {
|
|
492
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
493
|
+
process.stdout.write(USAGE);
|
|
494
|
+
if (args.length === 0) process.exitCode = 1;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let model: StatusModel;
|
|
499
|
+
try {
|
|
500
|
+
model = buildStatusModel(args[0]!);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
process.stderr.write(`ai task status: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
503
|
+
process.exitCode = 1;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
427
506
|
|
|
428
507
|
for (const line of renderStatus(model)) {
|
|
429
508
|
process.stdout.write(`${line}\n`);
|
|
@@ -433,12 +512,14 @@ function status(args: string[] = []): void {
|
|
|
433
512
|
export {
|
|
434
513
|
status,
|
|
435
514
|
makeRunner,
|
|
515
|
+
buildStatusModel,
|
|
436
516
|
collectMetadata,
|
|
437
517
|
groupArtifacts,
|
|
438
518
|
collectGit,
|
|
439
519
|
collectWorkflow,
|
|
440
520
|
collectRuntime,
|
|
441
521
|
renderStatus,
|
|
522
|
+
statusModelToDisplay,
|
|
442
523
|
METADATA_KEYS
|
|
443
524
|
};
|
|
444
|
-
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel };
|
|
525
|
+
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel, BuildStatusModelInput };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { extractSection } from './sections.ts';
|
|
2
|
+
|
|
3
|
+
const WORKFLOW_WARNING_HEADINGS = ['工作流告警', 'Workflow Warnings'];
|
|
4
|
+
const WORKFLOW_WARNING_STATUSES = new Set(['open', 'resolved', 'ignored']);
|
|
5
|
+
const WORKFLOW_WARNING_SEVERITIES = new Set(['IMPORTANT', 'ACTION_REQUIRED']);
|
|
6
|
+
|
|
7
|
+
type WorkflowWarning = {
|
|
8
|
+
id: string;
|
|
9
|
+
time: string;
|
|
10
|
+
step: string;
|
|
11
|
+
severity: string;
|
|
12
|
+
code: string;
|
|
13
|
+
status: string;
|
|
14
|
+
target: string;
|
|
15
|
+
message: string;
|
|
16
|
+
action: string;
|
|
17
|
+
resolvedAt: string;
|
|
18
|
+
resolution: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function splitTableRow(line: string): string[] {
|
|
22
|
+
let value = line.trim();
|
|
23
|
+
if (!value.startsWith('|')) return [];
|
|
24
|
+
value = value.replace(/^\|/, '').replace(/\|$/, '');
|
|
25
|
+
|
|
26
|
+
const cells: string[] = [];
|
|
27
|
+
let cell = '';
|
|
28
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
29
|
+
const char = value[index]!;
|
|
30
|
+
if (char === '|' && !isEscapedAt(value, index)) {
|
|
31
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
32
|
+
cell = '';
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
cell += char;
|
|
36
|
+
}
|
|
37
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
38
|
+
return cells;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function unescapeTableCell(value: string): string {
|
|
42
|
+
let output = '';
|
|
43
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
44
|
+
const char = value[index]!;
|
|
45
|
+
const next = value[index + 1];
|
|
46
|
+
if (char === '\\' && (next === '\\' || next === '|')) {
|
|
47
|
+
output += next;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
output += char;
|
|
52
|
+
}
|
|
53
|
+
return output;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isEscapedAt(value: string, index: number): boolean {
|
|
57
|
+
let backslashes = 0;
|
|
58
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\'; cursor -= 1) {
|
|
59
|
+
backslashes += 1;
|
|
60
|
+
}
|
|
61
|
+
return backslashes % 2 === 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isSeparatorRow(cells: string[]): boolean {
|
|
65
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses well-formed workflow warning rows. Malformed rows are intentionally
|
|
70
|
+
* ignored here; validate-artifact is responsible for reporting structure errors.
|
|
71
|
+
*/
|
|
72
|
+
function parseWorkflowWarnings(content: string): WorkflowWarning[] {
|
|
73
|
+
const section = extractSection(content, WORKFLOW_WARNING_HEADINGS);
|
|
74
|
+
if (!section) return [];
|
|
75
|
+
|
|
76
|
+
const rows: WorkflowWarning[] = [];
|
|
77
|
+
for (const rawLine of section.split(/\r?\n/)) {
|
|
78
|
+
const cells = splitTableRow(rawLine);
|
|
79
|
+
if (cells.length === 0) continue;
|
|
80
|
+
if ((cells[0] ?? '').toLowerCase() === 'id') continue;
|
|
81
|
+
if (isSeparatorRow(cells)) continue;
|
|
82
|
+
if (cells.length < 11) continue;
|
|
83
|
+
|
|
84
|
+
rows.push({
|
|
85
|
+
id: cells[0] ?? '',
|
|
86
|
+
time: cells[1] ?? '',
|
|
87
|
+
step: cells[2] ?? '',
|
|
88
|
+
severity: cells[3] ?? '',
|
|
89
|
+
code: cells[4] ?? '',
|
|
90
|
+
status: cells[5] ?? '',
|
|
91
|
+
target: cells[6] ?? '',
|
|
92
|
+
message: cells[7] ?? '',
|
|
93
|
+
action: cells[8] ?? '',
|
|
94
|
+
resolvedAt: cells[9] ?? '',
|
|
95
|
+
resolution: cells[10] ?? ''
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return rows;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getOpenWorkflowWarnings(content: string): WorkflowWarning[] {
|
|
102
|
+
return parseWorkflowWarnings(content).filter((warning) => warning.status === 'open');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatWorkflowWarningSummary(warnings: readonly WorkflowWarning[]): string[] {
|
|
106
|
+
return warnings.map((warning) => {
|
|
107
|
+
const target = warning.target ? ` ${warning.target}` : '';
|
|
108
|
+
const action = warning.action ? ` - ${warning.action}` : '';
|
|
109
|
+
return `${warning.id} [${warning.severity}] ${warning.code}${target}${action}`;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
WORKFLOW_WARNING_HEADINGS,
|
|
115
|
+
WORKFLOW_WARNING_STATUSES,
|
|
116
|
+
WORKFLOW_WARNING_SEVERITIES,
|
|
117
|
+
parseWorkflowWarnings,
|
|
118
|
+
getOpenWorkflowWarnings,
|
|
119
|
+
formatWorkflowWarningSummary
|
|
120
|
+
};
|
|
121
|
+
export type { WorkflowWarning };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Gemini CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@types/cross-spawn": "^6.0.6",
|
|
68
68
|
"@types/node": "^22.20.0",
|
|
69
69
|
"@types/semver": "^7.7.1",
|
|
70
|
-
"typescript": "~
|
|
70
|
+
"typescript": "~7.0"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
73
|
"@lydell/node-pty": "^1.2.0-beta.12"
|