@fitlab-ai/agent-infra 0.8.2 → 0.8.3
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/bin/cli.js +1 -1
- package/dist/lib/cp.js +1 -1
- package/dist/lib/decide.js +2 -2
- package/dist/lib/defaults.json +1 -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 +5 -5
- package/dist/lib/sandbox/clipboard/bridge.js +48 -13
- 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 +67 -65
- 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/tools.js +2 -2
- 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/defaults.json +1 -0
- package/lib/sandbox/clipboard/bridge.ts +50 -8
- 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 +84 -64
- 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/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/rules/create-issue.github.en.md +3 -3
- package/templates/.agents/rules/create-issue.github.zh-CN.md +3 -3
- 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/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +120 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +118 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +118 -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/create-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +7 -0
- 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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { resolveTaskRef } from
|
|
3
|
-
import { extractTitle } from
|
|
4
|
-
import { extractSection, findSectionHeading } from
|
|
5
|
-
import { renderTemplateBody, PLACEHOLDER } from
|
|
2
|
+
import { resolveTaskRef } from '../resolve-ref.js';
|
|
3
|
+
import { extractTitle } from '../frontmatter.js';
|
|
4
|
+
import { extractSection, findSectionHeading } from '../sections.js';
|
|
5
|
+
import { renderTemplateBody, PLACEHOLDER } from '../issue-form.js';
|
|
6
6
|
const USAGE = `Usage: ai task issue-body <N | #N | TASK-id> [--template <path>]
|
|
7
7
|
|
|
8
8
|
Print a deterministic Issue body extracted from a task's task.md.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { formatTable } from
|
|
3
|
-
import { resolveTaskRef } from
|
|
4
|
-
import { parseLedger
|
|
2
|
+
import { formatTable } from '../../table.js';
|
|
3
|
+
import { resolveTaskRef } from '../resolve-ref.js';
|
|
4
|
+
import { parseLedger } from '../ledger.js';
|
|
5
5
|
const USAGE = `Usage: ai task log <N | #N | TASK-id>
|
|
6
6
|
|
|
7
7
|
Renders a task's activity log as a per-step status table. A step's start and
|
|
@@ -13,7 +13,7 @@ Columns: # (row) / STEP / AGENT / STARTED / DONE / NOTE
|
|
|
13
13
|
A human-executed step shows AGENT as 'human' and, when it has no start marker,
|
|
14
14
|
a '-' STARTED placeholder. Review-step NOTE also carries two human counts in
|
|
15
15
|
the verdict list, right after blockers/major/minor: manual-validation
|
|
16
|
-
and human-decision (current ledger stage total).
|
|
16
|
+
and human-decision (current pending ledger stage total).
|
|
17
17
|
`;
|
|
18
18
|
const TABLE_HEADERS = ['#', 'STEP', 'AGENT', 'STARTED', 'DONE', 'NOTE'];
|
|
19
19
|
// The activity-log H2 heading is language-dependent (zh template / en template).
|
|
@@ -100,7 +100,7 @@ function pairEntries(entries) {
|
|
|
100
100
|
function countHumanDecisionsByStage(rows) {
|
|
101
101
|
const counts = new Map();
|
|
102
102
|
for (const row of rows) {
|
|
103
|
-
if (!isReviewStage(row.stage) ||
|
|
103
|
+
if (!isReviewStage(row.stage) || row.status !== 'needs-human-decision')
|
|
104
104
|
continue;
|
|
105
105
|
counts.set(row.stage, (counts.get(row.stage) ?? 0) + 1);
|
|
106
106
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { formatTable } from
|
|
5
|
-
import { parseTaskFrontmatter, extractTitle } from
|
|
6
|
-
import { loadShortIdByTaskId } from
|
|
4
|
+
import { formatTable } from '../../table.js';
|
|
5
|
+
import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
|
|
6
|
+
import { loadShortIdByTaskId } from '../short-id.js';
|
|
7
7
|
const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
|
|
8
8
|
|
|
9
9
|
Lists tasks under .agents/workspace/. Defaults to active tasks only.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { commandForEngine } from
|
|
5
|
-
import { resolveTaskRef } from
|
|
6
|
-
import { enumerateArtifacts } from
|
|
7
|
-
import { parseTaskFrontmatter, extractTitle } from
|
|
8
|
-
import { loadShortIdByTaskId } from
|
|
9
|
-
import {
|
|
4
|
+
import { commandForEngine } from '../../sandbox/shell.js';
|
|
5
|
+
import { resolveTaskRef } from '../resolve-ref.js';
|
|
6
|
+
import { enumerateArtifacts } from '../artifacts.js';
|
|
7
|
+
import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
|
|
8
|
+
import { loadShortIdByTaskId } from '../short-id.js';
|
|
9
|
+
import { getOpenWorkflowWarnings, formatWorkflowWarningSummary } from '../workflow-warnings.js';
|
|
10
|
+
import { parseActivityLog, pairEntries } from './log.js';
|
|
11
|
+
import { statusCard } from '../../server/display.js';
|
|
10
12
|
const USAGE = `Usage: ai task status <N | #N | TASK-id>
|
|
11
13
|
|
|
12
14
|
Prints an aggregated "health check" view for a task: header, metadata,
|
|
@@ -267,6 +269,9 @@ function renderStatus(model) {
|
|
|
267
269
|
if (model.title)
|
|
268
270
|
lines.push(model.title);
|
|
269
271
|
lines.push('', 'Metadata', ...renderPairs(model.metadata));
|
|
272
|
+
if (model.workflowWarnings.length > 0) {
|
|
273
|
+
lines.push('', `Workflow Warnings (${model.workflowWarnings.length} open)`, ...formatWorkflowWarningSummary(model.workflowWarnings).map((line) => ` ${line}`));
|
|
274
|
+
}
|
|
270
275
|
lines.push('', `Artifacts (${model.artifacts.count})`);
|
|
271
276
|
if (model.artifacts.groups.length === 0) {
|
|
272
277
|
lines.push(' (none)');
|
|
@@ -302,6 +307,59 @@ function renderStatus(model) {
|
|
|
302
307
|
]));
|
|
303
308
|
return lines;
|
|
304
309
|
}
|
|
310
|
+
function modelTone(model) {
|
|
311
|
+
if (model.workflow.state === 'in-progress')
|
|
312
|
+
return model.workflow.stale === 'yes' ? 'warning' : 'running';
|
|
313
|
+
if (model.metadata.some(([key, value]) => key === 'status' && value === 'completed'))
|
|
314
|
+
return 'success';
|
|
315
|
+
if (model.metadata.some(([key, value]) => key === 'status' && (value === 'blocked' || value === 'cancelled'))) {
|
|
316
|
+
return 'danger';
|
|
317
|
+
}
|
|
318
|
+
return 'info';
|
|
319
|
+
}
|
|
320
|
+
function buildFromResolved(input) {
|
|
321
|
+
const content = fs.readFileSync(input.taskMdPath, 'utf8');
|
|
322
|
+
const fm = parseTaskFrontmatter(content);
|
|
323
|
+
const run = input.run ?? makeRunner(input.repoRoot);
|
|
324
|
+
const artifacts = enumerateArtifacts(input.taskDir);
|
|
325
|
+
const workflow = collectWorkflow(content, input.now);
|
|
326
|
+
return {
|
|
327
|
+
taskId: input.taskId,
|
|
328
|
+
shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
|
|
329
|
+
title: extractTitle(content),
|
|
330
|
+
metadata: collectMetadata(fm),
|
|
331
|
+
workflowWarnings: getOpenWorkflowWarnings(content),
|
|
332
|
+
artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
|
|
333
|
+
workflow,
|
|
334
|
+
runtime: collectRuntime(input.taskDir, workflow, run),
|
|
335
|
+
git: collectGit(fm.branch ?? '', run)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function buildStatusModel(refOrInput, options = {}) {
|
|
339
|
+
if (typeof refOrInput !== 'string') {
|
|
340
|
+
return buildFromResolved(refOrInput);
|
|
341
|
+
}
|
|
342
|
+
const resolved = resolveTaskRef(refOrInput);
|
|
343
|
+
if (!resolved.ok) {
|
|
344
|
+
throw new Error(resolved.message);
|
|
345
|
+
}
|
|
346
|
+
return buildFromResolved({
|
|
347
|
+
taskId: resolved.taskId,
|
|
348
|
+
taskDir: resolved.taskDir,
|
|
349
|
+
taskMdPath: resolved.taskMdPath,
|
|
350
|
+
repoRoot: resolved.repoRoot,
|
|
351
|
+
run: options.run,
|
|
352
|
+
now: options.now
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function statusModelToDisplay(model) {
|
|
356
|
+
return statusCard(`Task ${model.taskId} (${model.shortId})`, modelTone(model), [
|
|
357
|
+
['workflow', model.workflow.state],
|
|
358
|
+
['step', model.workflow.step],
|
|
359
|
+
['runtime', model.runtime.status],
|
|
360
|
+
['git', model.git.uncommitted]
|
|
361
|
+
], renderStatus(model).join('\n'));
|
|
362
|
+
}
|
|
305
363
|
function status(args = []) {
|
|
306
364
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
307
365
|
process.stdout.write(USAGE);
|
|
@@ -309,30 +367,18 @@ function status(args = []) {
|
|
|
309
367
|
process.exitCode = 1;
|
|
310
368
|
return;
|
|
311
369
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
370
|
+
let model;
|
|
371
|
+
try {
|
|
372
|
+
model = buildStatusModel(args[0]);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
process.stderr.write(`ai task status: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
315
376
|
process.exitCode = 1;
|
|
316
377
|
return;
|
|
317
378
|
}
|
|
318
|
-
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
319
|
-
const fm = parseTaskFrontmatter(content);
|
|
320
|
-
const run = makeRunner(resolved.repoRoot);
|
|
321
|
-
const artifacts = enumerateArtifacts(resolved.taskDir);
|
|
322
|
-
const workflow = collectWorkflow(content);
|
|
323
|
-
const model = {
|
|
324
|
-
taskId: resolved.taskId,
|
|
325
|
-
shortId: loadShortIdByTaskId(resolved.repoRoot).get(resolved.taskId) ?? DASH,
|
|
326
|
-
title: extractTitle(content),
|
|
327
|
-
metadata: collectMetadata(fm),
|
|
328
|
-
artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
|
|
329
|
-
workflow,
|
|
330
|
-
runtime: collectRuntime(resolved.taskDir, workflow, run),
|
|
331
|
-
git: collectGit(fm.branch ?? '', run)
|
|
332
|
-
};
|
|
333
379
|
for (const line of renderStatus(model)) {
|
|
334
380
|
process.stdout.write(`${line}\n`);
|
|
335
381
|
}
|
|
336
382
|
}
|
|
337
|
-
export { status, makeRunner, collectMetadata, groupArtifacts, collectGit, collectWorkflow, collectRuntime, renderStatus, METADATA_KEYS };
|
|
383
|
+
export { status, makeRunner, buildStatusModel, collectMetadata, groupArtifacts, collectGit, collectWorkflow, collectRuntime, renderStatus, statusModelToDisplay, METADATA_KEYS };
|
|
338
384
|
//# sourceMappingURL=status.js.map
|
package/dist/lib/task/index.js
CHANGED
|
@@ -41,48 +41,48 @@ export async function runTask(args) {
|
|
|
41
41
|
}
|
|
42
42
|
switch (subcommand) {
|
|
43
43
|
case 'cat': {
|
|
44
|
-
const { cat } = await import(
|
|
44
|
+
const { cat } = await import('./commands/cat.js');
|
|
45
45
|
cat(rest);
|
|
46
46
|
break;
|
|
47
47
|
}
|
|
48
48
|
case 'decisions':
|
|
49
49
|
case 'd': {
|
|
50
|
-
const { decisions } = await import(
|
|
50
|
+
const { decisions } = await import('./commands/decisions.js');
|
|
51
51
|
decisions(rest);
|
|
52
52
|
break;
|
|
53
53
|
}
|
|
54
54
|
case 'files': {
|
|
55
|
-
const { files } = await import(
|
|
55
|
+
const { files } = await import('./commands/files.js');
|
|
56
56
|
files(rest);
|
|
57
57
|
break;
|
|
58
58
|
}
|
|
59
59
|
case 'grep': {
|
|
60
|
-
const { grep } = await import(
|
|
60
|
+
const { grep } = await import('./commands/grep.js');
|
|
61
61
|
grep(rest);
|
|
62
62
|
break;
|
|
63
63
|
}
|
|
64
64
|
case 'issue-body': {
|
|
65
|
-
const { issueBody } = await import(
|
|
65
|
+
const { issueBody } = await import('./commands/issue-body.js');
|
|
66
66
|
issueBody(rest);
|
|
67
67
|
break;
|
|
68
68
|
}
|
|
69
69
|
case 'log': {
|
|
70
|
-
const { log } = await import(
|
|
70
|
+
const { log } = await import('./commands/log.js');
|
|
71
71
|
log(rest);
|
|
72
72
|
break;
|
|
73
73
|
}
|
|
74
74
|
case 'ls': {
|
|
75
|
-
const { ls } = await import(
|
|
75
|
+
const { ls } = await import('./commands/ls.js');
|
|
76
76
|
ls(rest);
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
79
79
|
case 'show': {
|
|
80
|
-
const { show } = await import(
|
|
80
|
+
const { show } = await import('./commands/show.js');
|
|
81
81
|
show(rest);
|
|
82
82
|
break;
|
|
83
83
|
}
|
|
84
84
|
case 'status': {
|
|
85
|
-
const { status } = await import(
|
|
85
|
+
const { status } = await import('./commands/status.js');
|
|
86
86
|
status(rest);
|
|
87
87
|
break;
|
|
88
88
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
|
-
import { normalizeShortIdInput } from
|
|
4
|
+
import { normalizeShortIdInput } from './short-id.js';
|
|
5
5
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
6
|
// Flat-structured workspace dirs that hold tasks under `{dir}/{taskId}/task.md`.
|
|
7
7
|
// Note: `archive` uses a three-level YYYY/MM/DD layout and is handled separately.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { extractSection } from './sections.js';
|
|
2
|
+
const WORKFLOW_WARNING_HEADINGS = ['工作流告警', 'Workflow Warnings'];
|
|
3
|
+
const WORKFLOW_WARNING_STATUSES = new Set(['open', 'resolved', 'ignored']);
|
|
4
|
+
const WORKFLOW_WARNING_SEVERITIES = new Set(['IMPORTANT', 'ACTION_REQUIRED']);
|
|
5
|
+
function splitTableRow(line) {
|
|
6
|
+
let value = line.trim();
|
|
7
|
+
if (!value.startsWith('|'))
|
|
8
|
+
return [];
|
|
9
|
+
value = value.replace(/^\|/, '').replace(/\|$/, '');
|
|
10
|
+
const cells = [];
|
|
11
|
+
let cell = '';
|
|
12
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
13
|
+
const char = value[index];
|
|
14
|
+
if (char === '|' && !isEscapedAt(value, index)) {
|
|
15
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
16
|
+
cell = '';
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
cell += char;
|
|
20
|
+
}
|
|
21
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
22
|
+
return cells;
|
|
23
|
+
}
|
|
24
|
+
function unescapeTableCell(value) {
|
|
25
|
+
let output = '';
|
|
26
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
27
|
+
const char = value[index];
|
|
28
|
+
const next = value[index + 1];
|
|
29
|
+
if (char === '\\' && (next === '\\' || next === '|')) {
|
|
30
|
+
output += next;
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
output += char;
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
function isEscapedAt(value, index) {
|
|
39
|
+
let backslashes = 0;
|
|
40
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\'; cursor -= 1) {
|
|
41
|
+
backslashes += 1;
|
|
42
|
+
}
|
|
43
|
+
return backslashes % 2 === 1;
|
|
44
|
+
}
|
|
45
|
+
function isSeparatorRow(cells) {
|
|
46
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parses well-formed workflow warning rows. Malformed rows are intentionally
|
|
50
|
+
* ignored here; validate-artifact is responsible for reporting structure errors.
|
|
51
|
+
*/
|
|
52
|
+
function parseWorkflowWarnings(content) {
|
|
53
|
+
const section = extractSection(content, WORKFLOW_WARNING_HEADINGS);
|
|
54
|
+
if (!section)
|
|
55
|
+
return [];
|
|
56
|
+
const rows = [];
|
|
57
|
+
for (const rawLine of section.split(/\r?\n/)) {
|
|
58
|
+
const cells = splitTableRow(rawLine);
|
|
59
|
+
if (cells.length === 0)
|
|
60
|
+
continue;
|
|
61
|
+
if ((cells[0] ?? '').toLowerCase() === 'id')
|
|
62
|
+
continue;
|
|
63
|
+
if (isSeparatorRow(cells))
|
|
64
|
+
continue;
|
|
65
|
+
if (cells.length < 11)
|
|
66
|
+
continue;
|
|
67
|
+
rows.push({
|
|
68
|
+
id: cells[0] ?? '',
|
|
69
|
+
time: cells[1] ?? '',
|
|
70
|
+
step: cells[2] ?? '',
|
|
71
|
+
severity: cells[3] ?? '',
|
|
72
|
+
code: cells[4] ?? '',
|
|
73
|
+
status: cells[5] ?? '',
|
|
74
|
+
target: cells[6] ?? '',
|
|
75
|
+
message: cells[7] ?? '',
|
|
76
|
+
action: cells[8] ?? '',
|
|
77
|
+
resolvedAt: cells[9] ?? '',
|
|
78
|
+
resolution: cells[10] ?? ''
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
function getOpenWorkflowWarnings(content) {
|
|
84
|
+
return parseWorkflowWarnings(content).filter((warning) => warning.status === 'open');
|
|
85
|
+
}
|
|
86
|
+
function formatWorkflowWarningSummary(warnings) {
|
|
87
|
+
return warnings.map((warning) => {
|
|
88
|
+
const target = warning.target ? ` ${warning.target}` : '';
|
|
89
|
+
const action = warning.action ? ` - ${warning.action}` : '';
|
|
90
|
+
return `${warning.id} [${warning.severity}] ${warning.code}${target}${action}`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export { WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_STATUSES, WORKFLOW_WARNING_SEVERITIES, parseWorkflowWarnings, getOpenWorkflowWarnings, formatWorkflowWarningSummary };
|
|
94
|
+
//# sourceMappingURL=workflow-warnings.js.map
|
package/dist/lib/update.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { info, ok, err } from
|
|
4
|
-
import { resolveTemplateDir } from
|
|
5
|
-
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from
|
|
6
|
-
import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from
|
|
3
|
+
import { info, ok, err } from './log.js';
|
|
4
|
+
import { resolveTemplateDir } from './paths.js';
|
|
5
|
+
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.js';
|
|
6
|
+
import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from './builtin-tuis.js';
|
|
7
7
|
const defaults = JSON.parse(fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8'));
|
|
8
8
|
const CONFIG_DIR = '.agents';
|
|
9
9
|
const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
|
package/lib/defaults.json
CHANGED
|
@@ -30,8 +30,13 @@ type BridgeOptions = {
|
|
|
30
30
|
createDetector?: () => CtrlVDetector;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
type TextImageClipboardAdapter = ClipboardAdapter & {
|
|
34
|
+
readImageFromText?: (text: string) => Buffer | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
33
37
|
const FALLBACK_PREFIX = 'Warning: clipboard image paste bridge disabled';
|
|
34
38
|
const PARTIAL_ESCAPE_FLUSH_MS = 30;
|
|
39
|
+
const BRACKETED_PASTE_RE = /^\x1b\[200~([\s\S]*)\x1b\[201~$/u;
|
|
35
40
|
|
|
36
41
|
// Node's stdin.setRawMode(true) uses libuv's RAW mode, which (unlike the
|
|
37
42
|
// cfmakeraw that `docker exec -it` applies on the non-bridge path) keeps ONLCR
|
|
@@ -147,7 +152,7 @@ async function runBridge({
|
|
|
147
152
|
clearFlushTimer();
|
|
148
153
|
for (const token of detector.feed(inputDecoder.write(chunk))) {
|
|
149
154
|
if (token.kind === 'text') {
|
|
150
|
-
|
|
155
|
+
handleText(token.raw, child);
|
|
151
156
|
} else {
|
|
152
157
|
handleCtrlV(token, child);
|
|
153
158
|
}
|
|
@@ -157,7 +162,7 @@ async function runBridge({
|
|
|
157
162
|
flushTimer = null;
|
|
158
163
|
for (const token of detector.flush()) {
|
|
159
164
|
if (token.kind === 'text') {
|
|
160
|
-
|
|
165
|
+
handleText(token.raw, child);
|
|
161
166
|
} else {
|
|
162
167
|
handleCtrlV(token, child);
|
|
163
168
|
}
|
|
@@ -169,6 +174,37 @@ async function runBridge({
|
|
|
169
174
|
const onSigint = () => child.kill('SIGINT');
|
|
170
175
|
const onSigterm = () => child.kill('SIGTERM');
|
|
171
176
|
|
|
177
|
+
function writeImagePaste(png: Buffer, target: PtyProcess): void {
|
|
178
|
+
const filename = pngClipboardFilename(png);
|
|
179
|
+
writeClipboardPngAtomic(clipboardHostDir(home), filename, png);
|
|
180
|
+
pruneClipboardDir(clipboardHostDir(home));
|
|
181
|
+
target.write(buildBracketedPaste(containerClipboardPath(filename)));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function handleText(raw: string, target: PtyProcess): void {
|
|
185
|
+
const textAdapter = adapter as TextImageClipboardAdapter;
|
|
186
|
+
const pastedText = extractSinglePathPaste(raw);
|
|
187
|
+
if (!textAdapter.readImageFromText || pastedText === null) {
|
|
188
|
+
target.write(raw);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const png = textAdapter.readImageFromText(pastedText);
|
|
194
|
+
if (!png) {
|
|
195
|
+
target.write(raw);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
writeImagePaste(png, target);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
target.write(raw);
|
|
201
|
+
if (!warnedPasteFailure) {
|
|
202
|
+
warnedPasteFailure = true;
|
|
203
|
+
writeStderr(`Warning: clipboard image paste failed; forwarded pasted text (${error instanceof Error ? error.message : 'unknown error'})\n`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
172
208
|
function handleCtrlV(match: CtrlVMatch, target: PtyProcess): void {
|
|
173
209
|
try {
|
|
174
210
|
// readImagePng returns null both for "no image on clipboard" and for
|
|
@@ -181,10 +217,7 @@ async function runBridge({
|
|
|
181
217
|
target.write(match.raw);
|
|
182
218
|
return;
|
|
183
219
|
}
|
|
184
|
-
|
|
185
|
-
writeClipboardPngAtomic(clipboardHostDir(home), filename, png);
|
|
186
|
-
pruneClipboardDir(clipboardHostDir(home));
|
|
187
|
-
target.write(buildBracketedPaste(containerClipboardPath(filename)));
|
|
220
|
+
writeImagePaste(png, target);
|
|
188
221
|
} catch (error) {
|
|
189
222
|
target.write(match.raw);
|
|
190
223
|
if (!warnedPasteFailure) {
|
|
@@ -219,14 +252,14 @@ async function runBridge({
|
|
|
219
252
|
try {
|
|
220
253
|
for (const token of detector.feed(inputDecoder.end())) {
|
|
221
254
|
if (token.kind === 'text') {
|
|
222
|
-
|
|
255
|
+
handleText(token.raw, child);
|
|
223
256
|
} else {
|
|
224
257
|
handleCtrlV(token, child);
|
|
225
258
|
}
|
|
226
259
|
}
|
|
227
260
|
for (const token of detector.flush()) {
|
|
228
261
|
if (token.kind === 'text') {
|
|
229
|
-
|
|
262
|
+
handleText(token.raw, child);
|
|
230
263
|
}
|
|
231
264
|
}
|
|
232
265
|
} catch {
|
|
@@ -244,6 +277,15 @@ async function runBridge({
|
|
|
244
277
|
}
|
|
245
278
|
}
|
|
246
279
|
|
|
280
|
+
function extractSinglePathPaste(raw: string): string | null {
|
|
281
|
+
const match = raw.match(BRACKETED_PASTE_RE);
|
|
282
|
+
const text = (match ? match[1] : raw)?.trim();
|
|
283
|
+
if (!text || /[\r\n]/u.test(text)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return text;
|
|
287
|
+
}
|
|
288
|
+
|
|
247
289
|
function onceExit(
|
|
248
290
|
child: PtyProcess,
|
|
249
291
|
stdin: NodeJS.ReadStream
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { platform } from 'node:os';
|
|
2
2
|
import { createDarwinClipboardAdapter, type DarwinClipboardAdapter } from './darwin.ts';
|
|
3
|
+
import { createLinuxClipboardAdapter, type LinuxClipboardAdapter } from './linux.ts';
|
|
4
|
+
import { createWin32ClipboardAdapter, type Win32ClipboardAdapter } from './win32.ts';
|
|
3
5
|
|
|
4
|
-
export type ClipboardAdapter = DarwinClipboardAdapter;
|
|
6
|
+
export type ClipboardAdapter = DarwinClipboardAdapter | LinuxClipboardAdapter | Win32ClipboardAdapter;
|
|
5
7
|
|
|
6
8
|
export function createClipboardAdapter({
|
|
7
9
|
platformName = platform()
|
|
@@ -10,12 +12,9 @@ export function createClipboardAdapter({
|
|
|
10
12
|
case 'darwin':
|
|
11
13
|
return createDarwinClipboardAdapter();
|
|
12
14
|
case 'linux':
|
|
13
|
-
|
|
14
|
-
// see Issue #386 follow-up. Returning null disables the bridge for now.
|
|
15
|
-
return null;
|
|
15
|
+
return createLinuxClipboardAdapter();
|
|
16
16
|
case 'win32':
|
|
17
|
-
|
|
18
|
-
return null;
|
|
17
|
+
return createWin32ClipboardAdapter();
|
|
19
18
|
default:
|
|
20
19
|
return null;
|
|
21
20
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import type { ExecFileSyncOptions } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const PROBE_TIMEOUT_MS = 2_000;
|
|
8
|
+
const READ_IMAGE_TIMEOUT_MS = 5_000;
|
|
9
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
10
|
+
|
|
11
|
+
type ExecFn = (cmd: string, args: string[], options?: ExecFileSyncOptions) => Buffer | string;
|
|
12
|
+
type ExecToFileFn = (cmd: string, args: string[], outputPath: string, timeout: number) => void;
|
|
13
|
+
|
|
14
|
+
type LinuxBackend =
|
|
15
|
+
| { kind: 'wayland'; command: 'wl-paste' }
|
|
16
|
+
| { kind: 'x11'; command: 'xclip' };
|
|
17
|
+
|
|
18
|
+
export type LinuxClipboardAdapter = {
|
|
19
|
+
available(): { ok: true } | { ok: false; reason: string };
|
|
20
|
+
readImagePng(): Buffer | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function createLinuxClipboardAdapter({
|
|
24
|
+
env = process.env,
|
|
25
|
+
execFn = execFileSync,
|
|
26
|
+
execToFileFn = execToFile,
|
|
27
|
+
mkdtempFn = fs.mkdtempSync,
|
|
28
|
+
readFileFn = fs.readFileSync,
|
|
29
|
+
rmFn = fs.rmSync
|
|
30
|
+
}: {
|
|
31
|
+
env?: NodeJS.ProcessEnv;
|
|
32
|
+
execFn?: ExecFn;
|
|
33
|
+
execToFileFn?: ExecToFileFn;
|
|
34
|
+
mkdtempFn?: typeof fs.mkdtempSync;
|
|
35
|
+
readFileFn?: typeof fs.readFileSync;
|
|
36
|
+
rmFn?: typeof fs.rmSync;
|
|
37
|
+
} = {}): LinuxClipboardAdapter {
|
|
38
|
+
return {
|
|
39
|
+
available() {
|
|
40
|
+
const backend = selectBackend(env);
|
|
41
|
+
try {
|
|
42
|
+
execFn(backend.command, versionArgs(backend), {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
timeout: PROBE_TIMEOUT_MS
|
|
45
|
+
});
|
|
46
|
+
return { ok: true };
|
|
47
|
+
} catch {
|
|
48
|
+
return { ok: false, reason: unavailableReason(backend) };
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
readImagePng() {
|
|
52
|
+
const backend = selectBackend(env);
|
|
53
|
+
try {
|
|
54
|
+
const mimeTypes = String(execFn(backend.command, mimeArgs(backend), {
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
timeout: PROBE_TIMEOUT_MS
|
|
57
|
+
}));
|
|
58
|
+
if (!hasPngMime(mimeTypes)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const tmpDir = mkdtempFn(path.join(os.tmpdir(), 'agent-infra-clipboard-'));
|
|
63
|
+
const outputPath = path.join(tmpDir, 'clipboard.png');
|
|
64
|
+
try {
|
|
65
|
+
execToFileFn(backend.command, imageArgs(backend), outputPath, READ_IMAGE_TIMEOUT_MS);
|
|
66
|
+
const png = Buffer.from(readFileFn(outputPath));
|
|
67
|
+
return isPng(png) ? png : null;
|
|
68
|
+
} finally {
|
|
69
|
+
rmFn(tmpDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function selectBackend(env: NodeJS.ProcessEnv): LinuxBackend {
|
|
79
|
+
return env.WAYLAND_DISPLAY?.trim()
|
|
80
|
+
? { kind: 'wayland', command: 'wl-paste' }
|
|
81
|
+
: { kind: 'x11', command: 'xclip' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function versionArgs(backend: LinuxBackend): string[] {
|
|
85
|
+
return backend.kind === 'wayland' ? ['--version'] : ['-version'];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function mimeArgs(backend: LinuxBackend): string[] {
|
|
89
|
+
return backend.kind === 'wayland'
|
|
90
|
+
? ['--list-types']
|
|
91
|
+
: ['-selection', 'clipboard', '-t', 'TARGETS', '-o'];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function imageArgs(backend: LinuxBackend): string[] {
|
|
95
|
+
return backend.kind === 'wayland'
|
|
96
|
+
? ['-t', 'image/png']
|
|
97
|
+
: ['-selection', 'clipboard', '-t', 'image/png', '-o'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function unavailableReason(backend: LinuxBackend): string {
|
|
101
|
+
return backend.kind === 'wayland'
|
|
102
|
+
? 'Wayland clipboard tool wl-paste is unavailable; install wl-clipboard to enable image paste'
|
|
103
|
+
: 'X11 clipboard tool xclip is unavailable; install xclip to enable image paste';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hasPngMime(output: string): boolean {
|
|
107
|
+
return output.split(/\s+/u).some((type) => type === 'image/png');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function execToFile(cmd: string, args: string[], outputPath: string, timeout: number): void {
|
|
111
|
+
const fd = fs.openSync(outputPath, 'w', 0o600);
|
|
112
|
+
try {
|
|
113
|
+
execFileSync(cmd, args, {
|
|
114
|
+
timeout,
|
|
115
|
+
stdio: ['ignore', fd, 'pipe']
|
|
116
|
+
});
|
|
117
|
+
} finally {
|
|
118
|
+
fs.closeSync(fd);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isPng(buffer: Buffer): boolean {
|
|
123
|
+
return buffer.length >= PNG_MAGIC.length && PNG_MAGIC.every((byte, index) => buffer[index] === byte);
|
|
124
|
+
}
|