@fitlab-ai/agent-infra 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/cli.ts +3 -3
- package/dist/bin/cli.js +3 -3
- package/dist/lib/defaults.json +1 -0
- package/dist/lib/sandbox/commands/create.js +34 -1
- package/dist/lib/sandbox/config.js +1 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +1 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +1 -1
- package/dist/lib/sandbox/tools.js +9 -0
- package/dist/lib/server/adapters/feishu/index.js +5 -3
- package/dist/lib/server/adapters/feishu/transport.js +20 -2
- package/dist/lib/server/protocol.js +20 -4
- package/dist/lib/update.js +31 -0
- package/lib/defaults.json +1 -0
- package/lib/sandbox/commands/create.ts +39 -1
- package/lib/sandbox/config.ts +1 -1
- package/lib/sandbox/runtimes/node20.dockerfile +1 -1
- package/lib/sandbox/runtimes/node22.dockerfile +1 -1
- package/lib/sandbox/tools.ts +9 -0
- package/lib/server/adapters/feishu/index.ts +5 -3
- package/lib/server/adapters/feishu/transport.ts +32 -3
- package/lib/server/protocol.ts +20 -4
- package/lib/update.ts +34 -0
- package/package.json +2 -2
- package/templates/.agents/README.en.md +3 -3
- package/templates/.agents/README.zh-CN.md +3 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +26 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<a href="https://www.npmjs.com/package/@fitlab-ai/agent-infra"><img src="https://img.shields.io/npm/v/@fitlab-ai/agent-infra" alt="npm version"></a>
|
|
17
17
|
<a href="https://www.npmjs.com/package/@fitlab-ai/agent-infra"><img src="https://img.shields.io/npm/dm/@fitlab-ai/agent-infra" alt="npm downloads"></a>
|
|
18
18
|
<a href="License.txt"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
19
|
-
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-brightgreen?logo=node.js" alt="Node.js >= 22"></a>
|
|
19
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-%3E%3D22.9.0-brightgreen?logo=node.js" alt="Node.js >= 22.9.0"></a>
|
|
20
20
|
<a href="https://github.com/fitlab-ai/agent-infra/releases"><img src="https://img.shields.io/github/v/release/fitlab-ai/agent-infra" alt="GitHub release"></a>
|
|
21
21
|
<a href="https://codecov.io/gh/fitlab-ai/agent-infra"><img src="https://codecov.io/gh/fitlab-ai/agent-infra/graph/badge.svg" alt="codecov"></a>
|
|
22
22
|
<a href="CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"></a>
|
package/README.zh-CN.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<a href="https://www.npmjs.com/package/@fitlab-ai/agent-infra"><img src="https://img.shields.io/npm/v/@fitlab-ai/agent-infra" alt="npm version"></a>
|
|
17
17
|
<a href="https://www.npmjs.com/package/@fitlab-ai/agent-infra"><img src="https://img.shields.io/npm/dm/@fitlab-ai/agent-infra" alt="npm downloads"></a>
|
|
18
18
|
<a href="License.txt"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
19
|
-
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-brightgreen?logo=node.js" alt="Node.js >= 22"></a>
|
|
19
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-%3E%3D22.9.0-brightgreen?logo=node.js" alt="Node.js >= 22.9.0"></a>
|
|
20
20
|
<a href="https://github.com/fitlab-ai/agent-infra/releases"><img src="https://img.shields.io/github/v/release/fitlab-ai/agent-infra" alt="GitHub release"></a>
|
|
21
21
|
<a href="https://codecov.io/gh/fitlab-ai/agent-infra"><img src="https://codecov.io/gh/fitlab-ai/agent-infra/graph/badge.svg" alt="codecov"></a>
|
|
22
22
|
<a href="CONTRIBUTING.zh-CN.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"></a>
|
package/bin/cli.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { VERSION } from '../lib/version.ts';
|
|
3
3
|
|
|
4
4
|
// Node.js version check
|
|
5
|
-
const [major = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
|
|
6
|
-
if (major < 22) {
|
|
5
|
+
const [major = 0, minor = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
|
|
6
|
+
if (major < 22 || (major === 22 && minor < 9)) {
|
|
7
7
|
process.stderr.write(
|
|
8
|
-
`agent-infra requires Node.js >= 22 (current: ${process.version})\n`
|
|
8
|
+
`agent-infra requires Node.js >= 22.9.0 (current: ${process.version})\n`
|
|
9
9
|
);
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -9,9 +9,9 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
};
|
|
10
10
|
import { VERSION } from "../lib/version.js";
|
|
11
11
|
// Node.js version check
|
|
12
|
-
const [major = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
|
|
13
|
-
if (major < 22) {
|
|
14
|
-
process.stderr.write(`agent-infra requires Node.js >= 22 (current: ${process.version})\n`);
|
|
12
|
+
const [major = 0, minor = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
|
|
13
|
+
if (major < 22 || (major === 22 && minor < 9)) {
|
|
14
|
+
process.stderr.write(`agent-infra requires Node.js >= 22.9.0 (current: ${process.version})\n`);
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructure
|
package/dist/lib/defaults.json
CHANGED
|
@@ -678,6 +678,38 @@ function resolveHostCatalogPath(value, hostHomeDir) {
|
|
|
678
678
|
return null;
|
|
679
679
|
}
|
|
680
680
|
}
|
|
681
|
+
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'];
|
|
682
|
+
function removeCodexMcpServers(sandboxParsed) {
|
|
683
|
+
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
delete sandboxParsed.mcp_servers;
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
function inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) {
|
|
690
|
+
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
let sandboxFeatures = sandboxParsed.features;
|
|
697
|
+
let changed = false;
|
|
698
|
+
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
699
|
+
if (hostParsed.features[key] !== false) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (!sandboxFeatures) {
|
|
703
|
+
sandboxFeatures = {};
|
|
704
|
+
sandboxParsed.features = sandboxFeatures;
|
|
705
|
+
}
|
|
706
|
+
if (sandboxFeatures[key] !== false) {
|
|
707
|
+
sandboxFeatures[key] = false;
|
|
708
|
+
changed = true;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return changed;
|
|
712
|
+
}
|
|
681
713
|
export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex') {
|
|
682
714
|
if (!hostHomeDir) {
|
|
683
715
|
return;
|
|
@@ -704,12 +736,13 @@ export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodex
|
|
|
704
736
|
return;
|
|
705
737
|
}
|
|
706
738
|
}
|
|
739
|
+
let changed = removeCodexMcpServers(sandboxParsed);
|
|
740
|
+
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
707
741
|
const inheritSpecs = [
|
|
708
742
|
['model', 'string'],
|
|
709
743
|
['model_reasoning_effort', 'string'],
|
|
710
744
|
['model_auto_compact_token_limit', 'number']
|
|
711
745
|
];
|
|
712
|
-
let changed = false;
|
|
713
746
|
for (const [key, type] of inheritSpecs) {
|
|
714
747
|
if (Object.hasOwn(sandboxParsed, key)) {
|
|
715
748
|
continue;
|
|
@@ -10,7 +10,7 @@ import { parseCustomTools } from "./tools.js";
|
|
|
10
10
|
const DEFAULTS = Object.freeze({
|
|
11
11
|
engine: null,
|
|
12
12
|
runtimes: ['node22'],
|
|
13
|
-
tools: ['claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
13
|
+
tools: ['agent-infra', 'claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
14
14
|
dockerfile: null,
|
|
15
15
|
vm: {
|
|
16
16
|
cpu: null,
|
|
@@ -88,6 +88,15 @@ function createBuiltinTools(home, project) {
|
|
|
88
88
|
{ hostPath: hostJoin(home, '.gemini', 'settings.json'), sandboxName: 'settings.json' },
|
|
89
89
|
{ hostPath: hostJoin(home, '.gemini', 'google_accounts.json'), sandboxName: 'google_accounts.json' }
|
|
90
90
|
]
|
|
91
|
+
},
|
|
92
|
+
'agent-infra': {
|
|
93
|
+
id: 'agent-infra',
|
|
94
|
+
name: 'agent-infra CLI',
|
|
95
|
+
install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
|
|
96
|
+
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
|
|
97
|
+
containerMount: '/home/devuser/.agent-infra-cli',
|
|
98
|
+
versionCmd: 'ai version --raw',
|
|
99
|
+
setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
|
|
91
100
|
}
|
|
92
101
|
};
|
|
93
102
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createFeishuTransport, normalizeMessage } from "./transport.js";
|
|
1
|
+
import { cardMessage, createFeishuTransport, normalizeMessage } from "./transport.js";
|
|
2
2
|
// Assemble the feishu adapter. The transport is injectable so unit tests can
|
|
3
3
|
// drive start/dispatch/reply/sendMessage against a fake transport without the
|
|
4
4
|
// real SDK; production uses the default SDK-backed transport.
|
|
@@ -18,7 +18,9 @@ export function createFeishuAdapter(config, transport = createFeishuTransport(co
|
|
|
18
18
|
text: normalized.text,
|
|
19
19
|
messageId: normalized.messageId,
|
|
20
20
|
raw: normalized.raw,
|
|
21
|
-
reply: (text) =>
|
|
21
|
+
reply: async (text) => {
|
|
22
|
+
await transport.send(normalized.chatId, cardMessage(text));
|
|
23
|
+
}
|
|
22
24
|
};
|
|
23
25
|
await adapterCtx.dispatch(message);
|
|
24
26
|
}
|
|
@@ -31,7 +33,7 @@ export function createFeishuAdapter(config, transport = createFeishuTransport(co
|
|
|
31
33
|
await transport.stop();
|
|
32
34
|
},
|
|
33
35
|
async sendMessage(target, text) {
|
|
34
|
-
await transport.send(target.chatId, text);
|
|
36
|
+
await transport.send(target.chatId, cardMessage(text));
|
|
35
37
|
}
|
|
36
38
|
};
|
|
37
39
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as lark from '@larksuiteoapi/node-sdk';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
3
|
function asRecord(value) {
|
|
3
4
|
return typeof value === 'object' && value !== null ? value : {};
|
|
4
5
|
}
|
|
@@ -40,6 +41,23 @@ export function normalizeMessage(event) {
|
|
|
40
41
|
function resolveDomain(value) {
|
|
41
42
|
return value === 'lark' || value === 'Lark' ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
42
43
|
}
|
|
44
|
+
export function cleanFeishuText(text) {
|
|
45
|
+
return stripVTControlCharacters(text).replace(/\r\n/g, '\n');
|
|
46
|
+
}
|
|
47
|
+
export function cardMessage(text) {
|
|
48
|
+
return { kind: 'interactive', title: 'agent-infra', text: cleanFeishuText(text) };
|
|
49
|
+
}
|
|
50
|
+
export function toFeishuCreateData(chatId, message) {
|
|
51
|
+
return {
|
|
52
|
+
receive_id: chatId,
|
|
53
|
+
msg_type: 'interactive',
|
|
54
|
+
content: JSON.stringify({
|
|
55
|
+
config: { wide_screen_mode: true },
|
|
56
|
+
header: { title: { tag: 'plain_text', content: message.title }, template: 'blue' },
|
|
57
|
+
elements: [{ tag: 'div', text: { tag: 'lark_md', content: message.text } }]
|
|
58
|
+
})
|
|
59
|
+
};
|
|
60
|
+
}
|
|
43
61
|
// Build the real SDK-backed transport from adapter config. appId/appSecret come
|
|
44
62
|
// from server config (appSecret only via server.local.json / env). The WSClient
|
|
45
63
|
// long connection delivers events; the Client REST call sends replies.
|
|
@@ -84,10 +102,10 @@ export function createFeishuTransport(config) {
|
|
|
84
102
|
// Ignore: the daemon is shutting down regardless.
|
|
85
103
|
}
|
|
86
104
|
},
|
|
87
|
-
async send(chatId,
|
|
105
|
+
async send(chatId, message) {
|
|
88
106
|
await client.im.message.create({
|
|
89
107
|
params: { receive_id_type: 'chat_id' },
|
|
90
|
-
data:
|
|
108
|
+
data: toFeishuCreateData(chatId, message)
|
|
91
109
|
});
|
|
92
110
|
}
|
|
93
111
|
};
|
|
@@ -12,10 +12,26 @@ function taskSubcommand(subcommand) {
|
|
|
12
12
|
export function commandHelp() {
|
|
13
13
|
return [
|
|
14
14
|
`agent-infra ${VERSION}`,
|
|
15
|
-
'Built-ins:
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
15
|
+
'Built-ins:',
|
|
16
|
+
'/help',
|
|
17
|
+
'/ping',
|
|
18
|
+
'/version',
|
|
19
|
+
'Read:',
|
|
20
|
+
'/sandbox ls',
|
|
21
|
+
'/sandbox show {ref}',
|
|
22
|
+
'/sandbox vm status',
|
|
23
|
+
'/task decisions {ref}',
|
|
24
|
+
'/task log {ref}',
|
|
25
|
+
'/task ls',
|
|
26
|
+
'/task show {ref}',
|
|
27
|
+
'/task status {ref}',
|
|
28
|
+
'Write:',
|
|
29
|
+
'/sandbox create {ref}',
|
|
30
|
+
'/sandbox start {ref}',
|
|
31
|
+
'Exec:',
|
|
32
|
+
'/decide {task-ref} {HD-id} {decision}',
|
|
33
|
+
'/run create-task {description}',
|
|
34
|
+
'/run {skill} {task-ref}'
|
|
19
35
|
].join('\n');
|
|
20
36
|
}
|
|
21
37
|
export function parseCommand(text) {
|
package/dist/lib/update.js
CHANGED
|
@@ -7,6 +7,9 @@ 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');
|
|
10
|
+
const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
|
|
11
|
+
const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
|
|
12
|
+
const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
|
|
10
13
|
// One-time migration of the legacy project-level PR switch to the three-state
|
|
11
14
|
// `prFlow` preference. `true` (the old default / "PR flow on") maps to the
|
|
12
15
|
// strong constraint `required`; `false` maps to `disabled`. A missing or
|
|
@@ -25,6 +28,24 @@ function migratePrFlow(config) {
|
|
|
25
28
|
}
|
|
26
29
|
return null;
|
|
27
30
|
}
|
|
31
|
+
function isLegacyDefaultSandboxTools(value) {
|
|
32
|
+
if (!Array.isArray(value) || value.length !== LEGACY_DEFAULT_SANDBOX_TOOLS.length) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const tools = new Set(value);
|
|
36
|
+
return LEGACY_DEFAULT_SANDBOX_TOOLS.every((tool) => tools.has(tool));
|
|
37
|
+
}
|
|
38
|
+
function migrateSandboxTools(config) {
|
|
39
|
+
const tools = config.sandbox?.tools;
|
|
40
|
+
if (!isLegacyDefaultSandboxTools(tools)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
config.sandbox = {
|
|
44
|
+
...config.sandbox,
|
|
45
|
+
tools: [...DEFAULT_SANDBOX_TOOLS]
|
|
46
|
+
};
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
28
49
|
function isPathOwnedByOtherPlatform(relativePath, platformType) {
|
|
29
50
|
const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
|
|
30
51
|
if (!top.startsWith('.'))
|
|
@@ -153,6 +174,7 @@ async function cmdUpdate() {
|
|
|
153
174
|
const taskAdded = !config.task;
|
|
154
175
|
const labelsAdded = !config.labels;
|
|
155
176
|
const prFlowMigrated = migratePrFlow(config);
|
|
177
|
+
const sandboxToolsMigrated = !sandboxAdded && migrateSandboxTools(config);
|
|
156
178
|
let configChanged = changed;
|
|
157
179
|
if (platformAdded) {
|
|
158
180
|
config.platform = structuredClone(defaults.platform);
|
|
@@ -173,6 +195,9 @@ async function cmdUpdate() {
|
|
|
173
195
|
if (prFlowMigrated) {
|
|
174
196
|
configChanged = true;
|
|
175
197
|
}
|
|
198
|
+
if (sandboxToolsMigrated) {
|
|
199
|
+
configChanged = true;
|
|
200
|
+
}
|
|
176
201
|
if (configChanged) {
|
|
177
202
|
console.log('');
|
|
178
203
|
if (hasNewEntries) {
|
|
@@ -200,6 +225,9 @@ async function cmdUpdate() {
|
|
|
200
225
|
if (prFlowMigrated) {
|
|
201
226
|
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
202
227
|
}
|
|
228
|
+
if (sandboxToolsMigrated) {
|
|
229
|
+
info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
|
|
230
|
+
}
|
|
203
231
|
}
|
|
204
232
|
else {
|
|
205
233
|
info(`File registry changed in ${CONFIG_PATH}.`);
|
|
@@ -219,6 +247,9 @@ async function cmdUpdate() {
|
|
|
219
247
|
if (hasNewEntries && prFlowMigrated) {
|
|
220
248
|
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
221
249
|
}
|
|
250
|
+
if (hasNewEntries && sandboxToolsMigrated) {
|
|
251
|
+
info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
|
|
252
|
+
}
|
|
222
253
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
223
254
|
ok(`Updated ${CONFIG_PATH}`);
|
|
224
255
|
}
|
package/lib/defaults.json
CHANGED
|
@@ -915,6 +915,42 @@ function resolveHostCatalogPath(value: unknown, hostHomeDir: string): string | n
|
|
|
915
915
|
}
|
|
916
916
|
}
|
|
917
917
|
|
|
918
|
+
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'] as const;
|
|
919
|
+
|
|
920
|
+
function removeCodexMcpServers(sandboxParsed: JsonObject): boolean {
|
|
921
|
+
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
delete sandboxParsed.mcp_servers;
|
|
925
|
+
return true;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function inheritDisabledCodexFeatureFlags(sandboxParsed: JsonObject, hostParsed: JsonObject): boolean {
|
|
929
|
+
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
let sandboxFeatures = sandboxParsed.features as JsonObject | undefined;
|
|
937
|
+
let changed = false;
|
|
938
|
+
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
939
|
+
if (hostParsed.features[key] !== false) {
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (!sandboxFeatures) {
|
|
943
|
+
sandboxFeatures = {};
|
|
944
|
+
sandboxParsed.features = sandboxFeatures;
|
|
945
|
+
}
|
|
946
|
+
if (sandboxFeatures[key] !== false) {
|
|
947
|
+
sandboxFeatures[key] = false;
|
|
948
|
+
changed = true;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return changed;
|
|
952
|
+
}
|
|
953
|
+
|
|
918
954
|
export function ensureCodexModelInheritance(
|
|
919
955
|
toolDir: string,
|
|
920
956
|
hostHomeDir?: string,
|
|
@@ -947,13 +983,15 @@ export function ensureCodexModelInheritance(
|
|
|
947
983
|
}
|
|
948
984
|
}
|
|
949
985
|
|
|
986
|
+
let changed = removeCodexMcpServers(sandboxParsed);
|
|
987
|
+
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
988
|
+
|
|
950
989
|
const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
|
|
951
990
|
['model', 'string'],
|
|
952
991
|
['model_reasoning_effort', 'string'],
|
|
953
992
|
['model_auto_compact_token_limit', 'number']
|
|
954
993
|
];
|
|
955
994
|
|
|
956
|
-
let changed = false;
|
|
957
995
|
for (const [key, type] of inheritSpecs) {
|
|
958
996
|
if (Object.hasOwn(sandboxParsed, key)) {
|
|
959
997
|
continue;
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { SandboxTool } from './tools.ts';
|
|
|
12
12
|
const DEFAULTS = Object.freeze({
|
|
13
13
|
engine: null,
|
|
14
14
|
runtimes: ['node22'],
|
|
15
|
-
tools: ['claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
15
|
+
tools: ['agent-infra', 'claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
16
16
|
dockerfile: null,
|
|
17
17
|
vm: {
|
|
18
18
|
cpu: null,
|
package/lib/sandbox/tools.ts
CHANGED
|
@@ -124,6 +124,15 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
124
124
|
{ hostPath: hostJoin(home, '.gemini', 'settings.json'), sandboxName: 'settings.json' },
|
|
125
125
|
{ hostPath: hostJoin(home, '.gemini', 'google_accounts.json'), sandboxName: 'google_accounts.json' }
|
|
126
126
|
]
|
|
127
|
+
},
|
|
128
|
+
'agent-infra': {
|
|
129
|
+
id: 'agent-infra',
|
|
130
|
+
name: 'agent-infra CLI',
|
|
131
|
+
install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
|
|
132
|
+
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
|
|
133
|
+
containerMount: '/home/devuser/.agent-infra-cli',
|
|
134
|
+
versionCmd: 'ai version --raw',
|
|
135
|
+
setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
|
|
127
136
|
}
|
|
128
137
|
};
|
|
129
138
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Adapter, AdapterCtx, AdapterFactory, InboundMessage } from '../_contract.ts';
|
|
2
|
-
import { createFeishuTransport, normalizeMessage } from './transport.ts';
|
|
2
|
+
import { cardMessage, createFeishuTransport, normalizeMessage } from './transport.ts';
|
|
3
3
|
import type { FeishuTransport } from './transport.ts';
|
|
4
4
|
|
|
5
5
|
// Assemble the feishu adapter. The transport is injectable so unit tests can
|
|
@@ -25,7 +25,9 @@ export function createFeishuAdapter(
|
|
|
25
25
|
text: normalized.text,
|
|
26
26
|
messageId: normalized.messageId,
|
|
27
27
|
raw: normalized.raw,
|
|
28
|
-
reply: (text) =>
|
|
28
|
+
reply: async (text) => {
|
|
29
|
+
await transport.send(normalized.chatId, cardMessage(text));
|
|
30
|
+
}
|
|
29
31
|
};
|
|
30
32
|
await adapterCtx.dispatch(message);
|
|
31
33
|
} catch (error) {
|
|
@@ -37,7 +39,7 @@ export function createFeishuAdapter(
|
|
|
37
39
|
await transport.stop();
|
|
38
40
|
},
|
|
39
41
|
async sendMessage(target, text) {
|
|
40
|
-
await transport.send(target.chatId, text);
|
|
42
|
+
await transport.send(target.chatId, cardMessage(text));
|
|
41
43
|
}
|
|
42
44
|
};
|
|
43
45
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as lark from '@larksuiteoapi/node-sdk';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
3
|
|
|
3
4
|
// Transport layer for the feishu adapter. All @larksuiteoapi/node-sdk surface is
|
|
4
5
|
// confined here so the adapter body (index.ts) depends only on this narrow
|
|
@@ -10,7 +11,15 @@ export type FeishuTransport = {
|
|
|
10
11
|
// each inbound im.message.receive_v1.
|
|
11
12
|
start: (onMessage: (raw: unknown) => Promise<void>) => Promise<void>;
|
|
12
13
|
stop: () => Promise<void>;
|
|
13
|
-
send: (chatId: string,
|
|
14
|
+
send: (chatId: string, message: FeishuOutgoingMessage) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type FeishuOutgoingMessage = { kind: 'interactive'; title: string; text: string };
|
|
18
|
+
|
|
19
|
+
type FeishuCreateData = {
|
|
20
|
+
receive_id: string;
|
|
21
|
+
msg_type: 'interactive';
|
|
22
|
+
content: string;
|
|
14
23
|
};
|
|
15
24
|
|
|
16
25
|
export type NormalizedMessage = {
|
|
@@ -69,6 +78,26 @@ function resolveDomain(value: unknown): number {
|
|
|
69
78
|
return value === 'lark' || value === 'Lark' ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
70
79
|
}
|
|
71
80
|
|
|
81
|
+
export function cleanFeishuText(text: string): string {
|
|
82
|
+
return stripVTControlCharacters(text).replace(/\r\n/g, '\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function cardMessage(text: string): FeishuOutgoingMessage {
|
|
86
|
+
return { kind: 'interactive', title: 'agent-infra', text: cleanFeishuText(text) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function toFeishuCreateData(chatId: string, message: FeishuOutgoingMessage): FeishuCreateData {
|
|
90
|
+
return {
|
|
91
|
+
receive_id: chatId,
|
|
92
|
+
msg_type: 'interactive',
|
|
93
|
+
content: JSON.stringify({
|
|
94
|
+
config: { wide_screen_mode: true },
|
|
95
|
+
header: { title: { tag: 'plain_text', content: message.title }, template: 'blue' },
|
|
96
|
+
elements: [{ tag: 'div', text: { tag: 'lark_md', content: message.text } }]
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
72
101
|
// Build the real SDK-backed transport from adapter config. appId/appSecret come
|
|
73
102
|
// from server config (appSecret only via server.local.json / env). The WSClient
|
|
74
103
|
// long connection delivers events; the Client REST call sends replies.
|
|
@@ -118,10 +147,10 @@ export function createFeishuTransport(config: Record<string, unknown>): FeishuTr
|
|
|
118
147
|
// Ignore: the daemon is shutting down regardless.
|
|
119
148
|
}
|
|
120
149
|
},
|
|
121
|
-
async send(chatId,
|
|
150
|
+
async send(chatId, message) {
|
|
122
151
|
await client.im.message.create({
|
|
123
152
|
params: { receive_id_type: 'chat_id' },
|
|
124
|
-
data:
|
|
153
|
+
data: toFeishuCreateData(chatId, message)
|
|
125
154
|
});
|
|
126
155
|
}
|
|
127
156
|
};
|
package/lib/server/protocol.ts
CHANGED
|
@@ -23,10 +23,26 @@ function taskSubcommand(subcommand: string): string {
|
|
|
23
23
|
export function commandHelp(): string {
|
|
24
24
|
return [
|
|
25
25
|
`agent-infra ${VERSION}`,
|
|
26
|
-
'Built-ins:
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
26
|
+
'Built-ins:',
|
|
27
|
+
'/help',
|
|
28
|
+
'/ping',
|
|
29
|
+
'/version',
|
|
30
|
+
'Read:',
|
|
31
|
+
'/sandbox ls',
|
|
32
|
+
'/sandbox show {ref}',
|
|
33
|
+
'/sandbox vm status',
|
|
34
|
+
'/task decisions {ref}',
|
|
35
|
+
'/task log {ref}',
|
|
36
|
+
'/task ls',
|
|
37
|
+
'/task show {ref}',
|
|
38
|
+
'/task status {ref}',
|
|
39
|
+
'Write:',
|
|
40
|
+
'/sandbox create {ref}',
|
|
41
|
+
'/sandbox start {ref}',
|
|
42
|
+
'Exec:',
|
|
43
|
+
'/decide {task-ref} {HD-id} {decision}',
|
|
44
|
+
'/run create-task {description}',
|
|
45
|
+
'/run {skill} {task-ref}'
|
|
30
46
|
].join('\n');
|
|
31
47
|
}
|
|
32
48
|
|
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.
|
|
3
|
+
"version": "0.8.2",
|
|
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",
|
|
@@ -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}` -> `/
|
|
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": "
|
|
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`
|
|
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}` -> `/
|
|
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": "
|
|
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
|
-
|
|
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
|
|
|
@@ -29,6 +29,7 @@ const DEFAULTS = {
|
|
|
29
29
|
"node22"
|
|
30
30
|
],
|
|
31
31
|
"tools": [
|
|
32
|
+
"agent-infra",
|
|
32
33
|
"claude-code",
|
|
33
34
|
"codex",
|
|
34
35
|
"gemini-cli",
|
|
@@ -86,6 +87,9 @@ const DEFAULTS = {
|
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
const PACKAGE_NAME = '@fitlab-ai/agent-infra';
|
|
90
|
+
const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
|
|
91
|
+
const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
|
|
92
|
+
const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
|
|
89
93
|
// Add a new identifier here only after shipping matching .{platform}. template variants.
|
|
90
94
|
const KNOWN_PLATFORMS = new Set(['github']);
|
|
91
95
|
const KNOWN_LANGUAGES = new Set(['en', 'zh-CN']);
|
|
@@ -123,6 +127,26 @@ function isPathOwnedByDisabledTUI(rel, enabledSet) {
|
|
|
123
127
|
return false;
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
function isLegacyDefaultSandboxTools(value) {
|
|
131
|
+
if (!Array.isArray(value) || value.length !== LEGACY_DEFAULT_SANDBOX_TOOLS.length) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const tools = new Set(value);
|
|
135
|
+
return LEGACY_DEFAULT_SANDBOX_TOOLS.every(tool => tools.has(tool));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function migrateSandboxTools(cfg) {
|
|
139
|
+
const tools = cfg.sandbox?.tools;
|
|
140
|
+
if (!isLegacyDefaultSandboxTools(tools)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
cfg.sandbox = {
|
|
144
|
+
...cfg.sandbox,
|
|
145
|
+
tools: [...DEFAULT_SANDBOX_TOOLS]
|
|
146
|
+
};
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
function norm(p) { return p.replace(/\\/g, '/'); }
|
|
127
151
|
|
|
128
152
|
function normDir(p) {
|
|
@@ -1296,6 +1320,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1296
1320
|
) > 0;
|
|
1297
1321
|
|
|
1298
1322
|
const prevVersion = cfg.templateVersion;
|
|
1323
|
+
const sandboxToolsMigrated = migrateSandboxTools(cfg);
|
|
1299
1324
|
|
|
1300
1325
|
cfg.files.managed = managed;
|
|
1301
1326
|
cfg.files.merged = merged;
|
|
@@ -1303,7 +1328,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1303
1328
|
cfg.templateVersion = version;
|
|
1304
1329
|
delete cfg.templateSource;
|
|
1305
1330
|
|
|
1306
|
-
report.configUpdated = hasChanges || prevVersion !== version || hadTemplateSource;
|
|
1331
|
+
report.configUpdated = hasChanges || prevVersion !== version || hadTemplateSource || sandboxToolsMigrated;
|
|
1307
1332
|
|
|
1308
1333
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
1309
1334
|
|