@fitlab-ai/agent-infra 0.8.1 → 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.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/bin/cli.ts +3 -3
  4. package/dist/bin/cli.js +4 -4
  5. package/dist/lib/cp.js +1 -1
  6. package/dist/lib/decide.js +2 -2
  7. package/dist/lib/defaults.json +2 -0
  8. package/dist/lib/init.js +7 -7
  9. package/dist/lib/merge.js +1 -1
  10. package/dist/lib/prompt.js +1 -1
  11. package/dist/lib/run/index.js +7 -7
  12. package/dist/lib/run/prompt.js +1 -1
  13. package/dist/lib/sandbox/capture.js +5 -5
  14. package/dist/lib/sandbox/clipboard/bridge.js +48 -13
  15. package/dist/lib/sandbox/clipboard/index.js +5 -6
  16. package/dist/lib/sandbox/clipboard/linux.js +91 -0
  17. package/dist/lib/sandbox/clipboard/paths.js +1 -1
  18. package/dist/lib/sandbox/clipboard/win32.js +144 -0
  19. package/dist/lib/sandbox/commands/create.js +101 -66
  20. package/dist/lib/sandbox/commands/enter.js +9 -9
  21. package/dist/lib/sandbox/commands/list-running.js +2 -2
  22. package/dist/lib/sandbox/commands/ls.js +7 -7
  23. package/dist/lib/sandbox/commands/prune.js +8 -8
  24. package/dist/lib/sandbox/commands/rebuild.js +38 -50
  25. package/dist/lib/sandbox/commands/refresh.js +2 -2
  26. package/dist/lib/sandbox/commands/rm.js +11 -11
  27. package/dist/lib/sandbox/commands/show.js +4 -4
  28. package/dist/lib/sandbox/commands/start.js +4 -4
  29. package/dist/lib/sandbox/commands/vm.js +4 -4
  30. package/dist/lib/sandbox/config.js +11 -5
  31. package/dist/lib/sandbox/constants.js +4 -1
  32. package/dist/lib/sandbox/credentials.js +1 -1
  33. package/dist/lib/sandbox/dotfiles.js +1 -1
  34. package/dist/lib/sandbox/engine.js +3 -3
  35. package/dist/lib/sandbox/engines/index.js +5 -5
  36. package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
  37. package/dist/lib/sandbox/image-build.js +80 -0
  38. package/dist/lib/sandbox/image-prune.js +2 -2
  39. package/dist/lib/sandbox/index.js +10 -10
  40. package/dist/lib/sandbox/managed-fs.js +1 -1
  41. package/dist/lib/sandbox/readme-scaffold.js +1 -1
  42. package/dist/lib/sandbox/runtimes/node20.dockerfile +1 -1
  43. package/dist/lib/sandbox/runtimes/node22.dockerfile +1 -1
  44. package/dist/lib/sandbox/tools.js +11 -2
  45. package/dist/lib/server/adapters/feishu/index.js +12 -3
  46. package/dist/lib/server/adapters/feishu/renderer.js +88 -0
  47. package/dist/lib/server/adapters/feishu/transport.js +9 -2
  48. package/dist/lib/server/daemon.js +64 -43
  49. package/dist/lib/server/display.js +83 -0
  50. package/dist/lib/server/index.js +2 -2
  51. package/dist/lib/server/process-control.js +2 -2
  52. package/dist/lib/server/protocol.js +22 -6
  53. package/dist/lib/server/streamer.js +5 -4
  54. package/dist/lib/task/commands/cat.js +2 -2
  55. package/dist/lib/task/commands/decisions.js +4 -4
  56. package/dist/lib/task/commands/files.js +3 -3
  57. package/dist/lib/task/commands/grep.js +3 -3
  58. package/dist/lib/task/commands/issue-body.js +4 -4
  59. package/dist/lib/task/commands/log.js +5 -5
  60. package/dist/lib/task/commands/ls.js +3 -3
  61. package/dist/lib/task/commands/show.js +1 -1
  62. package/dist/lib/task/commands/status.js +71 -25
  63. package/dist/lib/task/index.js +9 -9
  64. package/dist/lib/task/resolve-ref.js +1 -1
  65. package/dist/lib/task/workflow-warnings.js +94 -0
  66. package/dist/lib/update.js +35 -4
  67. package/lib/defaults.json +2 -0
  68. package/lib/sandbox/clipboard/bridge.ts +50 -8
  69. package/lib/sandbox/clipboard/index.ts +5 -6
  70. package/lib/sandbox/clipboard/linux.ts +124 -0
  71. package/lib/sandbox/clipboard/win32.ts +173 -0
  72. package/lib/sandbox/commands/create.ts +123 -65
  73. package/lib/sandbox/commands/rebuild.ts +42 -54
  74. package/lib/sandbox/config.ts +14 -2
  75. package/lib/sandbox/constants.ts +4 -0
  76. package/lib/sandbox/image-build.ts +134 -0
  77. package/lib/sandbox/runtimes/node20.dockerfile +1 -1
  78. package/lib/sandbox/runtimes/node22.dockerfile +1 -1
  79. package/lib/sandbox/tools.ts +9 -0
  80. package/lib/server/adapters/_contract.ts +3 -0
  81. package/lib/server/adapters/feishu/index.ts +12 -2
  82. package/lib/server/adapters/feishu/renderer.ts +99 -0
  83. package/lib/server/adapters/feishu/transport.ts +19 -3
  84. package/lib/server/daemon.ts +74 -38
  85. package/lib/server/display.ts +136 -0
  86. package/lib/server/protocol.ts +20 -4
  87. package/lib/server/streamer.ts +5 -4
  88. package/lib/task/commands/log.ts +3 -3
  89. package/lib/task/commands/status.ts +102 -21
  90. package/lib/task/workflow-warnings.ts +121 -0
  91. package/lib/update.ts +34 -0
  92. package/package.json +3 -3
  93. package/templates/.agents/README.en.md +3 -3
  94. package/templates/.agents/README.zh-CN.md +3 -3
  95. package/templates/.agents/rules/create-issue.github.en.md +3 -3
  96. package/templates/.agents/rules/create-issue.github.zh-CN.md +3 -3
  97. package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
  98. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
  99. package/templates/.agents/rules/issue-sync.github.en.md +13 -0
  100. package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
  101. package/templates/.agents/rules/next-step-output.en.md +15 -1
  102. package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
  103. package/templates/.agents/rules/pr-sync.github.en.md +17 -2
  104. package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
  105. package/templates/.agents/rules/task-management.en.md +1 -1
  106. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  107. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
  108. package/templates/.agents/scripts/validate-artifact.js +120 -0
  109. package/templates/.agents/scripts/workflow-warnings.js +290 -0
  110. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +118 -0
  111. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +118 -0
  112. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
  113. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
  114. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
  115. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
  116. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
  117. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
  118. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
  119. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -0
  120. package/templates/.agents/skills/create-task/SKILL.en.md +4 -1
  121. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -1
  122. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +33 -1
  123. package/templates/.agents/templates/task.en.md +7 -0
  124. package/templates/.agents/templates/task.zh-CN.md +7 -0
  125. package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
  126. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
  127. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
  128. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
  129. package/templates/.github/workflows/metadata-sync.yml +5 -0
  130. package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
  131. package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
@@ -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 status(args: string[] = []): void {
398
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
399
- process.stdout.write(USAGE);
400
- if (args.length === 0) process.exitCode = 1;
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
- const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
432
+ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
433
+ const content = fs.readFileSync(input.taskMdPath, 'utf8');
412
434
  const fm = parseTaskFrontmatter(content);
413
- const run = makeRunner(resolved.repoRoot);
414
- const artifacts = enumerateArtifacts(resolved.taskDir);
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
- const model: StatusModel = {
418
- taskId: resolved.taskId,
419
- shortId: loadShortIdByTaskId(resolved.repoRoot).get(resolved.taskId) ?? DASH,
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(resolved.taskDir, workflow, run),
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/lib/update.ts CHANGED
@@ -40,6 +40,9 @@ const defaults = JSON.parse(
40
40
 
41
41
  const CONFIG_DIR = '.agents';
42
42
  const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
43
+ const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
44
+ const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
45
+ const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
43
46
 
44
47
  // One-time migration of the legacy project-level PR switch to the three-state
45
48
  // `prFlow` preference. `true` (the old default / "PR flow on") maps to the
@@ -60,6 +63,26 @@ function migratePrFlow(config: UpdateConfig): 'required' | 'disabled' | null {
60
63
  return null;
61
64
  }
62
65
 
66
+ function isLegacyDefaultSandboxTools(value: unknown): value is string[] {
67
+ if (!Array.isArray(value) || value.length !== LEGACY_DEFAULT_SANDBOX_TOOLS.length) {
68
+ return false;
69
+ }
70
+ const tools = new Set(value);
71
+ return LEGACY_DEFAULT_SANDBOX_TOOLS.every((tool) => tools.has(tool));
72
+ }
73
+
74
+ function migrateSandboxTools(config: UpdateConfig): boolean {
75
+ const tools = config.sandbox?.tools;
76
+ if (!isLegacyDefaultSandboxTools(tools)) {
77
+ return false;
78
+ }
79
+ config.sandbox = {
80
+ ...config.sandbox,
81
+ tools: [...DEFAULT_SANDBOX_TOOLS]
82
+ };
83
+ return true;
84
+ }
85
+
63
86
  function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
64
87
  const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
65
88
  if (!top.startsWith('.')) return false;
@@ -215,6 +238,7 @@ async function cmdUpdate(): Promise<void> {
215
238
  const taskAdded = !config.task;
216
239
  const labelsAdded = !config.labels;
217
240
  const prFlowMigrated = migratePrFlow(config);
241
+ const sandboxToolsMigrated = !sandboxAdded && migrateSandboxTools(config);
218
242
  let configChanged = changed;
219
243
 
220
244
  if (platformAdded) {
@@ -241,6 +265,10 @@ async function cmdUpdate(): Promise<void> {
241
265
  configChanged = true;
242
266
  }
243
267
 
268
+ if (sandboxToolsMigrated) {
269
+ configChanged = true;
270
+ }
271
+
244
272
  if (configChanged) {
245
273
  console.log('');
246
274
  if (hasNewEntries) {
@@ -267,6 +295,9 @@ async function cmdUpdate(): Promise<void> {
267
295
  if (prFlowMigrated) {
268
296
  info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
269
297
  }
298
+ if (sandboxToolsMigrated) {
299
+ info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
300
+ }
270
301
  } else {
271
302
  info(`File registry changed in ${CONFIG_PATH}.`);
272
303
  }
@@ -285,6 +316,9 @@ async function cmdUpdate(): Promise<void> {
285
316
  if (hasNewEntries && prFlowMigrated) {
286
317
  info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
287
318
  }
319
+ if (hasNewEntries && sandboxToolsMigrated) {
320
+ info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
321
+ }
288
322
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
289
323
  ok(`Updated ${CONFIG_PATH}`);
290
324
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
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",
@@ -29,7 +29,7 @@
29
29
  "templates/"
30
30
  ],
31
31
  "engines": {
32
- "node": ">=22"
32
+ "node": ">=22.9.0"
33
33
  },
34
34
  "keywords": [
35
35
  "ai",
@@ -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": "~6.0"
70
+ "typescript": "~7.0"
71
71
  },
72
72
  "optionalDependencies": {
73
73
  "@lydell/node-pty": "^1.2.0-beta.12"
@@ -260,7 +260,7 @@ Supported `invoke` placeholders:
260
260
  | Placeholder | Replaced with | Example |
261
261
  |-------------|---------------|---------|
262
262
  | `${skillName}` | The skill command name, such as `review-code` or `commit`. | `<your-cli> ${skillName}` -> `<your-cli> review-code` |
263
- | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/agent-infra:review-code` |
263
+ | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/your-project:review-code` |
264
264
 
265
265
  Non-namespaced custom TUI:
266
266
 
@@ -280,7 +280,7 @@ Namespaced custom TUI:
280
280
 
281
281
  ```json
282
282
  {
283
- "project": "agent-infra",
283
+ "project": "your-project",
284
284
  "customTUIs": [
285
285
  {
286
286
  "name": "<your-tui-name>",
@@ -295,7 +295,7 @@ Namespaced custom TUI:
295
295
 
296
296
  ## Sandbox Custom Tools
297
297
 
298
- `customTUIs` (above) generates slash-command files but does not change the sandbox image. To install a non-npm TUI (pip / cargo / curl-based / pre-built binary) into the sandbox image and live-mount its credentials, declare it under `sandbox.customTools` in `.agents/.airc.json`. Built-in tools (`claude-code`, `codex`, `opencode`, `gemini-cli`) keep working unchanged.
298
+ `customTUIs` generates slash-command files but does not change the sandbox image. To install a non-npm CLI or tool (pip / cargo / curl-based / pre-built binary) into the sandbox image and live-mount its credentials, declare it under `sandbox.customTools` in `.agents/.airc.json`. Built-in sandbox tools (`claude-code`, `codex`, `opencode`, `gemini-cli`, `agent-infra`) keep working unchanged; `agent-infra` only provides the in-sandbox `ai` / `agent-infra` CLI and is not part of `tuis` or `customTUIs`.
299
299
 
300
300
  ### Required fields
301
301
 
@@ -260,7 +260,7 @@ args: "<task-id>" # 可选
260
260
  | 占位符 | 替换为 | 示例 |
261
261
  |--------|--------|------|
262
262
  | `${skillName}` | skill 命令名,例如 `review-code` 或 `commit`。 | `<your-cli> ${skillName}` -> `<your-cli> review-code` |
263
- | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/agent-infra:review-code` |
263
+ | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/your-project:review-code` |
264
264
 
265
265
  不带命名空间的自定义 TUI:
266
266
 
@@ -280,7 +280,7 @@ args: "<task-id>" # 可选
280
280
 
281
281
  ```json
282
282
  {
283
- "project": "agent-infra",
283
+ "project": "your-project",
284
284
  "customTUIs": [
285
285
  {
286
286
  "name": "<your-tui-name>",
@@ -295,7 +295,7 @@ args: "<task-id>" # 可选
295
295
 
296
296
  ## 沙箱自定义工具(Sandbox Custom Tools)
297
297
 
298
- 上文 `customTUIs` 只负责生成 slash-command 文件,**不影响沙箱镜像**。如果要把一个非 npm 分发的 TUI(pip / cargo / curl 脚本 / 裸二进制)装进沙箱镜像、并 live-mount 它的凭证目录,需要在 `.agents/.airc.json` 的 `sandbox.customTools` 中声明。内建的四个工具(`claude-code` / `codex` / `opencode` / `gemini-cli`)行为保持不变。
298
+ `customTUIs` 只负责生成 slash-command 文件,**不影响沙箱镜像**。如果要把一个非 npm 分发的 CLI 或工具(pip / cargo / curl 脚本 / 裸二进制)装进沙箱镜像、并 live-mount 它的凭证目录,需要在 `.agents/.airc.json` 的 `sandbox.customTools` 中声明。内建 sandbox 工具(`claude-code` / `codex` / `opencode` / `gemini-cli` / `agent-infra`)行为保持不变;其中 `agent-infra` 只提供沙箱内 `ai` / `agent-infra` CLI,不属于 `tuis` 或 `customTUIs` 配置。
299
299
 
300
300
  ### 必填字段
301
301
 
@@ -101,7 +101,7 @@ If the final label set is empty, omit the `--label` argument.
101
101
  | `feature`, `enhancement` | `Feature` |
102
102
  | `task`, `documentation`, `dependency-upgrade`, `chore`, `docs`, `refactor`, `refactoring`, others | `Task` |
103
103
 
104
- When applying the Issue Type, follow the "Set Issue Type" command in `.agents/rules/issue-pr-commands.md`; first call `gh api orgs/{owner}/issue-types` to list the org's actually available Types, and only set the inferred value when it is present in that list. Failure to set is non-blocking.
104
+ When applying the Issue Type, follow the "Set Issue Type" command in `.agents/rules/issue-pr-commands.md`; only when the owner type is `Organization`, first call `gh api orgs/{owner}/issue-types` to list the org's actually available Types, and only set the inferred value when it is present in that list. User repositories, failed owner type probes, and failed writes are non-blocking.
105
105
 
106
106
  #### milestone
107
107
 
@@ -149,7 +149,7 @@ After success, parse the Issue number from the output (match only the `https://.
149
149
 
150
150
  ### 6. Set Issue Type (Optional)
151
151
 
152
- Execute only when `has_push=true` and the Issue Type inferred in §4 is in the org's actually available list:
152
+ Execute only when `has_push=true`, the owner type is `Organization`, and the Issue Type inferred in §4 is in the org's actually available list:
153
153
 
154
154
  ```bash
155
155
  gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
@@ -188,4 +188,4 @@ Hand the following back to the caller `create-task`:
188
188
  - Auth failure / command unavailable: return a structured `{code: "AUTH_FAILED", message}` to `create-task`; do not modify task.md
189
189
  - Network timeout / DNS failure: `{code: "NETWORK", message}`
190
190
  - Template parsing failure, Issue number parsing failure, other anomalies: `{code: "VALIDATION", message}`
191
- - All failures keep task.md untouched; `create-task` takes the "Scenario C: failure fallback" output branch and prompts the user to retry manually or fill `issue_number` later
191
+ - All failures keep task.md untouched; `create-task` must record the structured error in `## Workflow Warnings` (`severity=ACTION_REQUIRED`, `code=ISSUE_CREATE_FAILED`, `target=issue`) before taking the "Scenario C: failure fallback" output branch and prompting the user to retry manually or fill `issue_number` later
@@ -101,7 +101,7 @@ ai task issue-body {task-id} > "{body-file}"
101
101
  | `feature`, `enhancement` | `Feature` |
102
102
  | `task`, `documentation`, `dependency-upgrade`, `chore`, `docs`, `refactor`, `refactoring` 及其它 | `Task` |
103
103
 
104
- 实际设置时按 `.agents/rules/issue-pr-commands.md` 的 "设置 Issue Type" 命令;先调 `gh api orgs/{owner}/issue-types` 列出 org 实际可用的 Type,仅当推断值在列表中时才设置;设置失败不阻断流程。
104
+ 实际设置时按 `.agents/rules/issue-pr-commands.md` 的 "设置 Issue Type" 命令;仅当 owner type 为 `Organization` 时,先调 `gh api orgs/{owner}/issue-types` 列出 org 实际可用的 Type,并且仅当推断值在列表中时才设置;个人仓库、owner type 探测失败或设置失败都不阻断流程。
105
105
 
106
106
  #### milestone
107
107
 
@@ -149,7 +149,7 @@ gh issue create -R "$upstream_repo" \
149
149
 
150
150
  ### 6. 设置 Issue Type(可选)
151
151
 
152
- 仅当 `has_push=true` §4 推断的 Issue Type 在 org 实际可用列表中时执行:
152
+ 仅当 `has_push=true`、owner type 为 `Organization`,且 §4 推断的 Issue Type 在 org 实际可用列表中时执行:
153
153
 
154
154
  ```bash
155
155
  gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
@@ -188,4 +188,4 @@ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
188
188
  - 认证失败 / 命令不可用:返回结构化错误 `{code: "AUTH_FAILED", message}` 给 `create-task`,不修改 task.md
189
189
  - 网络超时 / DNS 失败:`{code: "NETWORK", message}`
190
190
  - 模板解析失败、Issue 编号解析失败、其它异常:`{code: "VALIDATION", message}`
191
- - 任意失败均不回滚 task.md;`create-task` "场景 C 失败兜底"输出,提示用户手动重试或在事后写入 `issue_number`
191
+ - 任意失败均不回滚 task.md;`create-task` 必须把结构化错误写入 `## 工作流告警`(`severity=ACTION_REQUIRED`,`code=ISSUE_CREATE_FAILED`,`target=issue`),再走"场景 C 失败兜底"输出,提示用户手动重试或在事后写入 `issue_number`
@@ -83,11 +83,16 @@ gh issue create -R "$upstream_repo" --title "{title}" --body "{body}" --assignee
83
83
  Set the Issue Type:
84
84
 
85
85
  ```bash
86
- gh api "orgs/{owner}/issue-types" --jq '.[].name'
87
- gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
86
+ owner_type=$(gh api "repos/$upstream_repo" --jq '.owner.type // empty' 2>/dev/null || true)
87
+ if [ "$owner_type" = "Organization" ]; then
88
+ owner=${upstream_repo%%/*}
89
+ gh api "orgs/$owner/issue-types" --jq '.[].name'
90
+ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
91
+ fi
88
92
  ```
89
93
 
90
94
  - set the Issue Type only when `has_push=true`; otherwise skip and continue
95
+ - query and set Issue Type only when the owner type is `Organization`; skip and continue for user repositories or failed owner type probes
91
96
  - when changing an existing Issue Type, read `.agents/rules/issue-fields.md` and use Flow B so same-name pinned fields are migrated and fields absent from the new type are cleared
92
97
 
93
98
  ## Update Issues
@@ -83,11 +83,16 @@ gh issue create -R "$upstream_repo" --title "{title}" --body "{body}" --assignee
83
83
  设置 Issue Type:
84
84
 
85
85
  ```bash
86
- gh api "orgs/{owner}/issue-types" --jq '.[].name'
87
- gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
86
+ owner_type=$(gh api "repos/$upstream_repo" --jq '.owner.type // empty' 2>/dev/null || true)
87
+ if [ "$owner_type" = "Organization" ]; then
88
+ owner=${upstream_repo%%/*}
89
+ gh api "orgs/$owner/issue-types" --jq '.[].name'
90
+ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
91
+ fi
88
92
  ```
89
93
 
90
94
  - 仅当 `has_push=true` 时执行 Issue Type 设置;否则跳过并继续
95
+ - 仅当 owner type 为 `Organization` 时查询和设置 Issue Type;个人仓库或 owner type 探测失败时跳过并继续
91
96
  - 变更现有 Issue Type 时,先读取 `.agents/rules/issue-fields.md` 并使用流程 B,确保同名 pinned fields 迁移,且新 type 不包含的字段被清空
92
97
 
93
98
  ## Issue 更新
@@ -66,6 +66,17 @@ Key rules:
66
66
  - insufficient permission only affects direct Issue metadata writes and must not stop the skill
67
67
  - keep the existing `2>/dev/null || true` error-tolerance pattern
68
68
 
69
+ When the caller has a `{task-id}` / task directory, permission degradation or critical sync failure must be recorded in `## Workflow Warnings`:
70
+
71
+ ```bash
72
+ node .agents/scripts/workflow-warnings.js add .agents/workspace/active/{task-id} \
73
+ --step issue-sync --severity IMPORTANT --code PERMISSION_DEGRADED \
74
+ --target "{operation}" --message "{reason}" \
75
+ --action "Wait for the bot/maintainer to backfill, or rerun the workflow step after permissions are available"
76
+ ```
77
+
78
+ Comment create/update failures and exhausted network retries that affect reviewer visibility use `severity=ACTION_REQUIRED` with `code=COMMENT_SYNC_FAILED` or `NETWORK_RETRY_EXHAUSTED`.
79
+
69
80
  ## External Contributor Locking
70
81
 
71
82
  Maintainers (`has_triage=true`) are never blocked. External contributors (`has_triage=false`) must check whether the current task already has a `task` comment author on the Issue before they start.
@@ -210,6 +221,8 @@ EOF
210
221
 
211
222
  Comment publishing is not gated by `has_triage` or `has_push`.
212
223
 
224
+ If comment lookup, creation, or update fails and the caller has a task directory, record a Workflow Warning (`step=issue-sync`, `severity=ACTION_REQUIRED`, `code=COMMENT_SYNC_FAILED`, `target={file-stem}`) and surface it in the final Workflow Warnings output block.
225
+
213
226
  ## task.md Comment Sync
214
227
 
215
228
  Hidden marker:
@@ -66,6 +66,17 @@ has_push=$(printf '%s' "$repo_perms" | grep -q '"push":true' 2>/dev/null && echo
66
66
  - 权限不足只影响直接写 Issue 元数据的步骤,不中断整个技能
67
67
  - 现有 `2>/dev/null || true` 容错模式保持不变
68
68
 
69
+ 当调用方存在 `{task-id}` / task 目录时,权限降级或关键同步失败必须写入 `## 工作流告警`:
70
+
71
+ ```bash
72
+ node .agents/scripts/workflow-warnings.js add .agents/workspace/active/{task-id} \
73
+ --step issue-sync --severity IMPORTANT --code PERMISSION_DEGRADED \
74
+ --target "{operation}" --message "{reason}" \
75
+ --action "等待 bot/维护者补位,或在具备权限后重跑对应 workflow 步骤"
76
+ ```
77
+
78
+ 评论创建 / 更新失败、网络重试耗尽等影响后续 reviewer 可见性的失败使用 `severity=ACTION_REQUIRED` 和 `code=COMMENT_SYNC_FAILED` 或 `NETWORK_RETRY_EXHAUSTED`。
79
+
69
80
  ## 外部开发者锁定机制
70
81
 
71
82
  维护者(`has_triage=true`)不受限制。外部开发者(`has_triage=false`)在开始任务前,必须先检查 Issue 上是否已有当前任务的 `task` 留言作者。
@@ -210,6 +221,8 @@ EOF
210
221
 
211
222
  评论发布不受 `has_triage` / `has_push` 限制,认证用户可正常执行。
212
223
 
224
+ 若评论查询、创建或更新失败且调用方有关联任务目录,记录 Workflow Warning(`step=issue-sync`、`severity=ACTION_REQUIRED`、`code=COMMENT_SYNC_FAILED`、`target={file-stem}`),并在最终输出的 Workflow Warnings 块提示人工处理。
225
+
213
226
  ## task.md 评论同步
214
227
 
215
228
  隐藏标记:
@@ -1,10 +1,11 @@
1
1
  # Next-Step Output Rule
2
2
 
3
- This file defines three **independent** rules for a skill's "notify-user / Next steps" output (the 3rd applies to review-* only); read this file before rendering the final output and apply whichever rules apply:
3
+ This file defines four **independent** rules for a skill's "notify-user / Next steps" output (the 3rd applies to review-* only); read this file before rendering the final output and apply whichever rules apply:
4
4
 
5
5
  1. **Next-step output structure**: how "Next steps" commands and the "Task info" block present the task ID (placeholders / short-id lookup / fallback).
6
6
  2. **Agent output trailing line (Completed at)**: the **very last line** of user-facing output, **independent of the "Next steps" block**, applying to normal / error / early-return paths alike.
7
7
  3. **Pending human-decision pre-block**: applies only to `review-analysis` / `review-plan` / `review-code` when this stage has pending rulings (`{h} > 0`) — expand the pending items before the "Next steps" commands and prompt to resolve them first.
8
+ 4. **Workflow Warnings output block**: applies when task.md has `status=open` rows in `## Workflow Warnings` — print the warning summary after all normal information and "Next steps" commands, and before `Completed at`.
8
9
 
9
10
  ## Placeholder semantics
10
11
 
@@ -62,6 +63,19 @@ Completed at: YYYY-MM-DD HH:mm:ss
62
63
  - Position: it must be the last line of the entire user-facing output, after all "Next steps" commands. If a scenario has a conditional reminder line after the commands (e.g. the manual-validation reminder), the completion line goes after that reminder.
63
64
  - This line is for terminal scanning only; it is never written to any artifact file or Issue/PR comment. The single source of truth for completion time remains the Activity Log in task.md.
64
65
 
66
+ ## Workflow Warnings Output Block
67
+
68
+ If the current task's `## Workflow Warnings` / `## 工作流告警` table has any `status=open` row, the skill's final user output must append a summary block after all normal information and "Next steps" commands, and before `Completed at`. When no open warning exists, do not render this block.
69
+
70
+ Format:
71
+
72
+ ```text
73
+ [ACTION REQUIRED] Workflow warnings are open:
74
+ - WW-N {code} ({target}): {action}
75
+ ```
76
+
77
+ Use `[ACTION REQUIRED]` when any open row has `severity=ACTION_REQUIRED`; otherwise use `[IMPORTANT]`. `{code}`, `{target}`, and `{action}` come directly from the warning table columns.
78
+
65
79
  ## Pending human-decision pre-block (review-* only, when {h} > 0)
66
80
 
67
81
  This section is a **third standalone rule, co-equal with the two above**, used only by the "notify-user / report conclusion" step of `review-analysis` / `review-plan` / `review-code`.