teamcopilot 0.0.2 → 0.1.0
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 +6 -26
- package/dist/frontend/assets/{cssMode-BDT3WbVs.js → cssMode-Bsa7tmw2.js} +1 -1
- package/dist/frontend/assets/{freemarker2-C7-hEgID.js → freemarker2-CrKZYA9-.js} +1 -1
- package/dist/frontend/assets/{handlebars-4cwTkPir.js → handlebars-BgBTN99O.js} +1 -1
- package/dist/frontend/assets/{html-YNfE1Q0A.js → html-D36ptcmC.js} +1 -1
- package/dist/frontend/assets/{htmlMode-opTQ1HoB.js → htmlMode-CGVK_FcF.js} +1 -1
- package/dist/frontend/assets/{index-DWyaVa1h.js → index-BcB_fBN5.js} +133 -133
- package/dist/frontend/assets/index-Ds8n3J4W.css +1 -0
- package/dist/frontend/assets/{javascript-BEwGzk7T.js → javascript-C6sCQzuh.js} +1 -1
- package/dist/frontend/assets/{jsonMode-CGhIS5Al.js → jsonMode-Bh2liG7i.js} +1 -1
- package/dist/frontend/assets/{liquid-QekTGCGJ.js → liquid-CvdwL9kp.js} +1 -1
- package/dist/frontend/assets/{mdx-BAVDaB7v.js → mdx-CdZzWG3e.js} +1 -1
- package/dist/frontend/assets/{python-BQlHw7XO.js → python-CSUJS9Bk.js} +1 -1
- package/dist/frontend/assets/{razor-Be3Wwc2E.js → razor-ypTGnW2v.js} +1 -1
- package/dist/frontend/assets/{tsMode-CIBFoN3z.js → tsMode-Cr9H7DaY.js} +1 -1
- package/dist/frontend/assets/{typescript-BuV9wEIE.js → typescript-wuTifRF-.js} +1 -1
- package/dist/frontend/assets/{xml-DcDKYaM4.js → xml-DoFtLqDh.js} +1 -1
- package/dist/frontend/assets/{yaml-CuBNmOuI.js → yaml-CrPwKSmj.js} +1 -1
- package/dist/frontend/index.html +2 -2
- package/dist/opencode-auth/index.js +26 -3
- package/dist/opencode-server.js +5 -0
- package/dist/utils/approval-snapshot-common.js +29 -16
- package/dist/utils/opencode-auth.js +112 -0
- package/dist/utils/resource-files.js +22 -21
- package/dist/utils/skill.js +39 -9
- package/dist/utils/workspace-sync.js +12 -7
- package/dist/workspace_files/.opencode/opencode.json +4 -1
- package/dist/workspace_files/.opencode/package.json +2 -1
- package/dist/workspace_files/.opencode/plugins/createSkill.ts +38 -7
- package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +34 -3
- package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +33 -2
- package/dist/workspace_files/.opencode/plugins/findSkill.ts +36 -5
- package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +34 -3
- package/dist/workspace_files/.opencode/plugins/honeytoken-protection.ts +1 -1
- package/dist/workspace_files/.opencode/plugins/listAvailableSkills.ts +33 -2
- package/dist/workspace_files/.opencode/plugins/listAvailableWorkflows.ts +33 -2
- package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +34 -3
- package/dist/workspace_files/.opencode/plugins/skill-command-guard.ts +138 -0
- package/dist/workspace_files/AGENTS.md +3 -3
- package/dist/workspace_files/package-lock.json +2 -1
- package/dist/workspace_files/package.json +3 -1
- package/package.json +1 -1
- package/dist/frontend/assets/index-lXrsgeTF.css +0 -1
|
@@ -62,6 +62,32 @@ function assertNoSymlink(absolutePath, resourceLabel) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
function resolveSnapshotRootPath(rootPath, slug, resourceLabel) {
|
|
66
|
+
if (!fs_1.default.existsSync(rootPath)) {
|
|
67
|
+
throw {
|
|
68
|
+
status: 404,
|
|
69
|
+
message: `${capitalize(resourceLabel)} not found for slug: ${slug}`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const lstat = fs_1.default.lstatSync(rootPath);
|
|
73
|
+
if (lstat.isSymbolicLink()) {
|
|
74
|
+
const resolvedPath = fs_1.default.realpathSync(rootPath);
|
|
75
|
+
if (!fs_1.default.statSync(resolvedPath).isDirectory()) {
|
|
76
|
+
throw {
|
|
77
|
+
status: 400,
|
|
78
|
+
message: `${capitalize(resourceLabel)} path is not a directory`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return resolvedPath;
|
|
82
|
+
}
|
|
83
|
+
if (!lstat.isDirectory()) {
|
|
84
|
+
throw {
|
|
85
|
+
status: 400,
|
|
86
|
+
message: `${capitalize(resourceLabel)} path is not a directory`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return rootPath;
|
|
90
|
+
}
|
|
65
91
|
function ensureDirectoryExists(absoluteDir) {
|
|
66
92
|
fs_1.default.mkdirSync(absoluteDir, { recursive: true });
|
|
67
93
|
}
|
|
@@ -150,20 +176,8 @@ function computeSnapshotHash(files) {
|
|
|
150
176
|
return hash.digest("hex");
|
|
151
177
|
}
|
|
152
178
|
function collectCurrentResourceSnapshot(options) {
|
|
153
|
-
const { root_path:
|
|
154
|
-
|
|
155
|
-
throw {
|
|
156
|
-
status: 404,
|
|
157
|
-
message: `${capitalize(resourceLabel)} not found for slug: ${slug}`
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
assertNoSymlink(rootPath, resourceLabel);
|
|
161
|
-
if (!fs_1.default.statSync(rootPath).isDirectory()) {
|
|
162
|
-
throw {
|
|
163
|
-
status: 400,
|
|
164
|
-
message: `${capitalize(resourceLabel)} path is not a directory`
|
|
165
|
-
};
|
|
166
|
-
}
|
|
179
|
+
const { root_path: configuredRootPath, slug, resource_label: resourceLabel } = options;
|
|
180
|
+
const rootPath = resolveSnapshotRootPath(configuredRootPath, slug, resourceLabel);
|
|
167
181
|
const files = [];
|
|
168
182
|
collectFilesRecursive(rootPath, "", files, resourceLabel);
|
|
169
183
|
files.sort((a, b) => a.relative_path.localeCompare(b.relative_path));
|
|
@@ -322,8 +336,7 @@ async function restoreResourceToApprovedSnapshot(config, userId) {
|
|
|
322
336
|
message: "No approved snapshot exists to restore"
|
|
323
337
|
};
|
|
324
338
|
}
|
|
325
|
-
const rootPath = config.root_path;
|
|
326
|
-
assertNoSymlink(rootPath, config.resource_label);
|
|
339
|
+
const rootPath = resolveSnapshotRootPath(config.root_path, config.slug, config.resource_label);
|
|
327
340
|
const currentPaths = [];
|
|
328
341
|
collectCurrentNonIgnoredFilePaths(rootPath, "", currentPaths, config.resource_label);
|
|
329
342
|
const snapshotPathSet = new Set(snapshot.files.map((file) => file.relative_path));
|
|
@@ -5,14 +5,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getConfiguredModelProviderId = getConfiguredModelProviderId;
|
|
7
7
|
exports.initializeOpencodeAuthStorage = initializeOpencodeAuthStorage;
|
|
8
|
+
exports.isServiceManagedProvider = isServiceManagedProvider;
|
|
9
|
+
exports.hasRuntimeProviderCredentials = hasRuntimeProviderCredentials;
|
|
8
10
|
exports.getRuntimeProviderAuth = getRuntimeProviderAuth;
|
|
9
11
|
exports.setRuntimeProviderAuth = setRuntimeProviderAuth;
|
|
12
|
+
exports.syncManagedProviderConfiguration = syncManagedProviderConfiguration;
|
|
10
13
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
14
|
const path_1 = __importDefault(require("path"));
|
|
12
15
|
const assert_1 = require("./assert");
|
|
13
16
|
const workspace_sync_1 = require("./workspace-sync");
|
|
14
17
|
const WORKSPACE_OPENCODE_DIR = ".opencode";
|
|
15
18
|
const WORKSPACE_AUTH_FILE = "auth.json";
|
|
19
|
+
const WORKSPACE_CONFIG_FILE = "opencode.json";
|
|
16
20
|
const RUNTIME_DATA_HOME_DIR = "xdg-data";
|
|
17
21
|
function getWorkspaceOpencodeDir() {
|
|
18
22
|
return path_1.default.join((0, workspace_sync_1.getWorkspaceDirFromEnv)(), WORKSPACE_OPENCODE_DIR);
|
|
@@ -23,6 +27,9 @@ function getRuntimeDataHomePath() {
|
|
|
23
27
|
function getRuntimeAuthPath() {
|
|
24
28
|
return path_1.default.join(getRuntimeDataHomePath(), "opencode", WORKSPACE_AUTH_FILE);
|
|
25
29
|
}
|
|
30
|
+
function getWorkspaceOpencodeConfigPath() {
|
|
31
|
+
return path_1.default.join(getWorkspaceOpencodeDir(), WORKSPACE_CONFIG_FILE);
|
|
32
|
+
}
|
|
26
33
|
function normalizeProviderId(providerId) {
|
|
27
34
|
return providerId.replace(/\/+$/, "");
|
|
28
35
|
}
|
|
@@ -80,6 +87,35 @@ async function writeAuthRecord(filepath, data) {
|
|
|
80
87
|
await promises_1.default.rename(tempPath, filepath);
|
|
81
88
|
await promises_1.default.chmod(filepath, 0o600).catch(() => { });
|
|
82
89
|
}
|
|
90
|
+
async function readOpencodeConfig(filepath) {
|
|
91
|
+
try {
|
|
92
|
+
const content = await promises_1.default.readFile(filepath, "utf-8");
|
|
93
|
+
const parsed = JSON.parse(content);
|
|
94
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
95
|
+
throw new Error("Invalid opencode config");
|
|
96
|
+
}
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const nodeError = err;
|
|
101
|
+
if (nodeError.code === "ENOENT") {
|
|
102
|
+
return {
|
|
103
|
+
$schema: "https://opencode.ai/config.json",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function writeOpencodeConfig(filepath, data) {
|
|
110
|
+
await promises_1.default.mkdir(path_1.default.dirname(filepath), { recursive: true });
|
|
111
|
+
const tempPath = `${filepath}.tmp-${process.pid}-${Date.now()}`;
|
|
112
|
+
await promises_1.default.writeFile(tempPath, `${JSON.stringify(data, null, 2)}\n`, {
|
|
113
|
+
encoding: "utf-8",
|
|
114
|
+
mode: 0o600,
|
|
115
|
+
});
|
|
116
|
+
await promises_1.default.rename(tempPath, filepath);
|
|
117
|
+
await promises_1.default.chmod(filepath, 0o600).catch(() => { });
|
|
118
|
+
}
|
|
83
119
|
function getConfiguredModelProviderId() {
|
|
84
120
|
const model = (0, assert_1.assertEnv)("OPENCODE_MODEL");
|
|
85
121
|
const [providerId, ...parts] = model.split("/");
|
|
@@ -88,6 +124,14 @@ function getConfiguredModelProviderId() {
|
|
|
88
124
|
}
|
|
89
125
|
return providerId;
|
|
90
126
|
}
|
|
127
|
+
function getConfiguredModelId() {
|
|
128
|
+
const model = (0, assert_1.assertEnv)("OPENCODE_MODEL");
|
|
129
|
+
const [providerId, ...parts] = model.split("/");
|
|
130
|
+
if (!providerId || parts.length === 0) {
|
|
131
|
+
throw new Error("OPENCODE_MODEL must be in the format <provider>/<model>");
|
|
132
|
+
}
|
|
133
|
+
return parts.join("/");
|
|
134
|
+
}
|
|
91
135
|
function configureOpencodeDataHome() {
|
|
92
136
|
const runtimeDataHome = getRuntimeDataHomePath();
|
|
93
137
|
process.env.XDG_DATA_HOME = runtimeDataHome;
|
|
@@ -112,6 +156,40 @@ function getAuthForProvider(record, providerId) {
|
|
|
112
156
|
const normalizedProviderId = normalizeProviderId(providerId);
|
|
113
157
|
return record[providerId] || record[normalizedProviderId] || record[`${normalizedProviderId}/`];
|
|
114
158
|
}
|
|
159
|
+
function isAzureCustomProvider(providerId) {
|
|
160
|
+
return normalizeProviderId(providerId).toLowerCase() === "azure-openai";
|
|
161
|
+
}
|
|
162
|
+
function isServiceManagedProvider(providerId) {
|
|
163
|
+
return isAzureCustomProvider(providerId);
|
|
164
|
+
}
|
|
165
|
+
function normalizeAzureEndpoint(endpoint) {
|
|
166
|
+
return endpoint.trim().replace(/\/+$/, "");
|
|
167
|
+
}
|
|
168
|
+
function hasRequiredAzureEnvironment() {
|
|
169
|
+
return isNonEmptyString(process.env.AZURE_API_KEY)
|
|
170
|
+
&& isNonEmptyString(process.env.AZURE_OPENAI_ENDPOINT)
|
|
171
|
+
&& isNonEmptyString(process.env.AZURE_OPENAI_API_VERSION);
|
|
172
|
+
}
|
|
173
|
+
async function hasAzureProviderConfiguration(providerId) {
|
|
174
|
+
const deployment = getConfiguredModelId().trim();
|
|
175
|
+
const config = await readOpencodeConfig(getWorkspaceOpencodeConfigPath());
|
|
176
|
+
const provider = config.provider?.[normalizeProviderId(providerId)];
|
|
177
|
+
if (!provider || provider.npm !== "@ai-sdk/azure") {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
const models = provider.models;
|
|
181
|
+
if (!models || typeof models !== "object" || Array.isArray(models)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const deploymentConfig = models[deployment];
|
|
185
|
+
return Boolean(deploymentConfig && typeof deploymentConfig === "object" && !Array.isArray(deploymentConfig));
|
|
186
|
+
}
|
|
187
|
+
async function hasRuntimeProviderCredentials(providerId) {
|
|
188
|
+
if (isAzureCustomProvider(providerId)) {
|
|
189
|
+
return hasRequiredAzureEnvironment() && await hasAzureProviderConfiguration(providerId);
|
|
190
|
+
}
|
|
191
|
+
return Boolean(await getRuntimeProviderAuth(providerId));
|
|
192
|
+
}
|
|
115
193
|
async function getRuntimeProviderAuth(providerId) {
|
|
116
194
|
const record = await readAuthRecord(getRuntimeAuthPath());
|
|
117
195
|
return getAuthForProvider(record, providerId);
|
|
@@ -124,3 +202,37 @@ async function setRuntimeProviderAuth(providerId, info) {
|
|
|
124
202
|
runtimeRecord[normalizedProviderId] = info;
|
|
125
203
|
await writeAuthRecord(getRuntimeAuthPath(), runtimeRecord);
|
|
126
204
|
}
|
|
205
|
+
async function syncManagedProviderConfiguration() {
|
|
206
|
+
const providerId = getConfiguredModelProviderId();
|
|
207
|
+
if (!isAzureCustomProvider(providerId) || !hasRequiredAzureEnvironment()) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const endpoint = normalizeAzureEndpoint((0, assert_1.assertEnv)("AZURE_OPENAI_ENDPOINT"));
|
|
211
|
+
const apiVersion = (0, assert_1.assertEnv)("AZURE_OPENAI_API_VERSION").trim();
|
|
212
|
+
const deployment = getConfiguredModelId().trim();
|
|
213
|
+
const normalizedProviderId = normalizeProviderId(providerId);
|
|
214
|
+
const configPath = getWorkspaceOpencodeConfigPath();
|
|
215
|
+
const configRecord = await readOpencodeConfig(configPath);
|
|
216
|
+
const providerRecord = configRecord.provider ?? {};
|
|
217
|
+
const existingProviderConfig = providerRecord[normalizedProviderId];
|
|
218
|
+
configRecord.provider = {
|
|
219
|
+
...providerRecord,
|
|
220
|
+
[normalizedProviderId]: {
|
|
221
|
+
...(existingProviderConfig ?? {}),
|
|
222
|
+
npm: "@ai-sdk/azure",
|
|
223
|
+
name: "Azure OpenAI",
|
|
224
|
+
models: {
|
|
225
|
+
[deployment]: {
|
|
226
|
+
name: deployment,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
options: {
|
|
230
|
+
...(existingProviderConfig?.options ?? {}),
|
|
231
|
+
baseURL: `${endpoint}/openai`,
|
|
232
|
+
apiVersion,
|
|
233
|
+
useDeploymentBasedUrls: true,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
await writeOpencodeConfig(configPath, configRecord);
|
|
238
|
+
}
|
|
@@ -11,8 +11,22 @@ const redact_1 = require("./redact");
|
|
|
11
11
|
function createResourceFileManager(options) {
|
|
12
12
|
const { getResourcePath, resourceLabel, editorLabel } = options;
|
|
13
13
|
const rootLabel = `${resourceLabel} root`;
|
|
14
|
-
const symlinkRootError = `${editorLabel} editor does not support symlinked ${resourceLabel} directories`;
|
|
15
14
|
const symlinkError = `${editorLabel} editor does not support symlinks`;
|
|
15
|
+
function getResolvedRootPaths(slug) {
|
|
16
|
+
const configuredRoot = getResourcePath(slug);
|
|
17
|
+
try {
|
|
18
|
+
return {
|
|
19
|
+
configuredRoot,
|
|
20
|
+
realRoot: fs_1.default.realpathSync(configuredRoot),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw {
|
|
25
|
+
status: 404,
|
|
26
|
+
message: `${rootLabel} not found`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
function toEtag(buffer) {
|
|
17
31
|
return crypto_1.default.createHash("sha256").update(buffer).digest("hex");
|
|
18
32
|
}
|
|
@@ -64,21 +78,8 @@ function createResourceFileManager(options) {
|
|
|
64
78
|
}
|
|
65
79
|
return absolute;
|
|
66
80
|
}
|
|
67
|
-
function assertRootNotSymlink(slug) {
|
|
68
|
-
const resourceRoot = getResourcePath(slug);
|
|
69
|
-
if (!fs_1.default.existsSync(resourceRoot)) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const rootLstat = fs_1.default.lstatSync(resourceRoot);
|
|
73
|
-
if (rootLstat.isSymbolicLink()) {
|
|
74
|
-
throw {
|
|
75
|
-
status: 400,
|
|
76
|
-
message: symlinkRootError
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
81
|
function assertRealPathWithinRoot(slug, absolutePath) {
|
|
81
|
-
const realRoot =
|
|
82
|
+
const { realRoot } = getResolvedRootPaths(slug);
|
|
82
83
|
const realTarget = fs_1.default.realpathSync(absolutePath);
|
|
83
84
|
if (realTarget !== realRoot && !realTarget.startsWith(`${realRoot}${path_1.default.sep}`)) {
|
|
84
85
|
throw {
|
|
@@ -88,9 +89,14 @@ function createResourceFileManager(options) {
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
function assertNoSymlinkInAncestors(slug, absolutePath) {
|
|
91
|
-
const
|
|
92
|
+
const { configuredRoot: rawConfiguredRoot, realRoot: rawRealRoot } = getResolvedRootPaths(slug);
|
|
93
|
+
const configuredRoot = path_1.default.resolve(rawConfiguredRoot);
|
|
94
|
+
const realRoot = path_1.default.resolve(rawRealRoot);
|
|
92
95
|
let current = path_1.default.resolve(absolutePath);
|
|
93
96
|
while (true) {
|
|
97
|
+
if (current === configuredRoot || current === realRoot) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
94
100
|
const lstat = fs_1.default.lstatSync(current);
|
|
95
101
|
if (lstat.isSymbolicLink()) {
|
|
96
102
|
throw {
|
|
@@ -98,9 +104,6 @@ function createResourceFileManager(options) {
|
|
|
98
104
|
message: symlinkError
|
|
99
105
|
};
|
|
100
106
|
}
|
|
101
|
-
if (current === root) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
107
|
const parent = path_1.default.dirname(current);
|
|
105
108
|
if (parent === current) {
|
|
106
109
|
throw {
|
|
@@ -112,12 +115,10 @@ function createResourceFileManager(options) {
|
|
|
112
115
|
}
|
|
113
116
|
}
|
|
114
117
|
function assertExistingPathIsSafe(slug, absolutePath) {
|
|
115
|
-
assertRootNotSymlink(slug);
|
|
116
118
|
assertRealPathWithinRoot(slug, absolutePath);
|
|
117
119
|
assertNoSymlinkInAncestors(slug, absolutePath);
|
|
118
120
|
}
|
|
119
121
|
function assertParentDirectoryIsSafeForCreate(slug, parentAbsolutePath) {
|
|
120
|
-
assertRootNotSymlink(slug);
|
|
121
122
|
assertRealPathWithinRoot(slug, parentAbsolutePath);
|
|
122
123
|
assertNoSymlinkInAncestors(slug, parentAbsolutePath);
|
|
123
124
|
}
|
package/dist/utils/skill.js
CHANGED
|
@@ -15,7 +15,7 @@ const client_1 = __importDefault(require("../prisma/client"));
|
|
|
15
15
|
const workspace_sync_1 = require("./workspace-sync");
|
|
16
16
|
const permission_common_1 = require("./permission-common");
|
|
17
17
|
function getSkillsRootPath() {
|
|
18
|
-
return path_1.default.join((0, workspace_sync_1.getWorkspaceDirFromEnv)(), "
|
|
18
|
+
return path_1.default.join((0, workspace_sync_1.getWorkspaceDirFromEnv)(), ".agents", "skills");
|
|
19
19
|
}
|
|
20
20
|
function getSkillPath(slug) {
|
|
21
21
|
return path_1.default.join(getSkillsRootPath(), slug);
|
|
@@ -66,9 +66,19 @@ function listSkillSlugs() {
|
|
|
66
66
|
const entries = fs_1.default.readdirSync(skillsDir, { withFileTypes: true });
|
|
67
67
|
const slugs = [];
|
|
68
68
|
for (const entry of entries) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const skillEntryPath = path_1.default.join(skillsDir, entry.name);
|
|
70
|
+
if (!entry.isDirectory()) {
|
|
71
|
+
if (!entry.isSymbolicLink())
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
if (!fs_1.default.statSync(skillEntryPath).isDirectory())
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const canonicalManifestPath = path_1.default.join(skillEntryPath, "SKILL.md");
|
|
72
82
|
if (fs_1.default.existsSync(canonicalManifestPath)) {
|
|
73
83
|
slugs.push(entry.name);
|
|
74
84
|
}
|
|
@@ -76,12 +86,32 @@ function listSkillSlugs() {
|
|
|
76
86
|
return slugs;
|
|
77
87
|
}
|
|
78
88
|
function extractFrontmatterValue(frontmatter, key) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
const lines = frontmatter.split("\n");
|
|
90
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
91
|
+
const line = lines[index];
|
|
92
|
+
const match = line.match(new RegExp(`^${key}\\s*:\\s*(.*)$`));
|
|
93
|
+
if (!match) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const rawValue = match[1]?.trim() ?? "";
|
|
97
|
+
if (rawValue === "|" || rawValue === ">") {
|
|
98
|
+
const blockLines = [];
|
|
99
|
+
for (let blockIndex = index + 1; blockIndex < lines.length; blockIndex += 1) {
|
|
100
|
+
const blockLine = lines[blockIndex] ?? "";
|
|
101
|
+
if (blockLine.length === 0) {
|
|
102
|
+
blockLines.push("");
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!/^\s+/.test(blockLine)) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
blockLines.push(blockLine.replace(/^\s+/, ""));
|
|
109
|
+
}
|
|
110
|
+
return blockLines.join(rawValue === ">" ? " " : "\n").trim();
|
|
111
|
+
}
|
|
112
|
+
return rawValue.replace(/^["']|["']$/g, "");
|
|
83
113
|
}
|
|
84
|
-
return
|
|
114
|
+
return null;
|
|
85
115
|
}
|
|
86
116
|
function readSkillManifest(slug) {
|
|
87
117
|
const skillManifestPath = getSkillManifestPath(slug);
|
|
@@ -21,6 +21,7 @@ const WORKSPACE_DB_DIRECTORY = ".sqlite";
|
|
|
21
21
|
const WORKSPACE_DB_FILENAME = "data.db";
|
|
22
22
|
const HONEYTOKEN_UUID = "1f9f0b72-5f9f-4c9b-aef1-2fb2e0f6d8c4";
|
|
23
23
|
const HONEYTOKEN_FILE_NAME = `honeytoken-${HONEYTOKEN_UUID}.txt`;
|
|
24
|
+
const WORKSPACE_AZURE_PROVIDER_VERSION = "3.0.48";
|
|
24
25
|
function getWorkspaceDirFromEnv() {
|
|
25
26
|
let workspaceDir = (0, assert_1.assertEnv)("WORKSPACE_DIR");
|
|
26
27
|
if (!path_1.default.isAbsolute(workspaceDir)) {
|
|
@@ -48,13 +49,11 @@ function ensureWorkspaceDatabaseDirectory() {
|
|
|
48
49
|
function normalizeRelativePath(relativePath) {
|
|
49
50
|
return relativePath.split(path_1.default.sep).join("/");
|
|
50
51
|
}
|
|
51
|
-
const MANAGED_WORKSPACE_DIRECTORIES = new Set([
|
|
52
|
-
"workflows",
|
|
53
|
-
"custom-skills",
|
|
54
|
-
]);
|
|
55
52
|
function shouldSkipManagedDirectoryContent(relativePath) {
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
if (relativePath === "workflows" || relativePath.startsWith("workflows/")) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (relativePath === ".agents/skills" || relativePath.startsWith(".agents/skills/")) {
|
|
58
57
|
return true;
|
|
59
58
|
}
|
|
60
59
|
if (relativePath === ".opencode/xdg-data" || relativePath.startsWith(".opencode/xdg-data/")) {
|
|
@@ -178,6 +177,9 @@ function syncTemplateDirectory(sourceDirectory, targetDirectory, relativePath, i
|
|
|
178
177
|
mergeGitignoreFile(sourceEntryPath, targetEntryPath);
|
|
179
178
|
continue;
|
|
180
179
|
}
|
|
180
|
+
if (relativeEntryPath === ".opencode/opencode.json" && fs_1.default.existsSync(targetEntryPath)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
181
183
|
if (entry.name === "package.json") {
|
|
182
184
|
mergePackageJsonFile(sourceEntryPath, targetEntryPath);
|
|
183
185
|
continue;
|
|
@@ -195,6 +197,9 @@ async function initializeWorkspaceNodeDependencies(workspaceDir) {
|
|
|
195
197
|
...(existingPackageJson.dependencies ?? {}),
|
|
196
198
|
"opencode-ai": "1.1.65",
|
|
197
199
|
};
|
|
200
|
+
if ((0, assert_1.assertEnv)("OPENCODE_MODEL").startsWith("azure-openai/")) {
|
|
201
|
+
dependencies["@ai-sdk/azure"] = WORKSPACE_AZURE_PROVIDER_VERSION;
|
|
202
|
+
}
|
|
198
203
|
const workspacePackageJson = {
|
|
199
204
|
...existingPackageJson,
|
|
200
205
|
dependencies,
|
|
@@ -210,7 +215,7 @@ async function initializeWorkspaceDirectory() {
|
|
|
210
215
|
fs_1.default.mkdirSync(workspaceDir, { recursive: true });
|
|
211
216
|
const workflowsDir = path_1.default.join(workspaceDir, "workflows");
|
|
212
217
|
fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
213
|
-
const skillsDir = path_1.default.join(workspaceDir, "
|
|
218
|
+
const skillsDir = path_1.default.join(workspaceDir, ".agents", "skills");
|
|
214
219
|
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
215
220
|
const honeytokenValue = `DO_NOT_EXPOSE:${HONEYTOKEN_UUID}\n`;
|
|
216
221
|
fs_1.default.writeFileSync(path_1.default.join(workflowsDir, HONEYTOKEN_FILE_NAME), honeytokenValue, "utf-8");
|
|
@@ -24,6 +24,14 @@ interface PermissionResponse {
|
|
|
24
24
|
approved: boolean
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
interface SessionLookupResponse {
|
|
28
|
+
error?: unknown
|
|
29
|
+
data?: {
|
|
30
|
+
id?: string
|
|
31
|
+
parentID?: string
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
async function readErrorMessageFromResponse(
|
|
28
36
|
response: Response,
|
|
29
37
|
fallbackMessage: string
|
|
@@ -191,7 +199,29 @@ function buildSkillMarkdown(
|
|
|
191
199
|
return `---\nname: ${JSON.stringify(slug)}\ndescription: ${JSON.stringify(description)}\n---\n\n${body}\n`
|
|
192
200
|
}
|
|
193
201
|
|
|
194
|
-
export const CreateSkillPlugin: Plugin = async (
|
|
202
|
+
export const CreateSkillPlugin: Plugin = async ({ client }) => {
|
|
203
|
+
async function resolveRootSessionID(sessionID: string): Promise<string> {
|
|
204
|
+
let currentSessionID = sessionID
|
|
205
|
+
|
|
206
|
+
while (true) {
|
|
207
|
+
const response = (await client.session.get({
|
|
208
|
+
path: {
|
|
209
|
+
id: currentSessionID,
|
|
210
|
+
},
|
|
211
|
+
})) as SessionLookupResponse
|
|
212
|
+
if (response.error) {
|
|
213
|
+
throw new Error(`Failed to resolve root session for ${currentSessionID}`)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const parentID = response.data?.parentID
|
|
217
|
+
if (!parentID) {
|
|
218
|
+
return currentSessionID
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
currentSessionID = parentID
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
195
225
|
return {
|
|
196
226
|
tool: {
|
|
197
227
|
createSkill: tool({
|
|
@@ -210,6 +240,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
210
240
|
},
|
|
211
241
|
async execute(args, context) {
|
|
212
242
|
const { directory, sessionID } = context
|
|
243
|
+
const authSessionID = await resolveRootSessionID(sessionID)
|
|
213
244
|
const slug = args.slug.trim()
|
|
214
245
|
const description = args.description.trim()
|
|
215
246
|
const content = args.content.trim()
|
|
@@ -238,7 +269,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
238
269
|
throw new Error("Could not determine call id from tool context.")
|
|
239
270
|
}
|
|
240
271
|
|
|
241
|
-
const skillsDir = path.join(directory, "
|
|
272
|
+
const skillsDir = path.join(directory, ".agents", "skills")
|
|
242
273
|
const skillDir = path.join(skillsDir, slug)
|
|
243
274
|
|
|
244
275
|
if (!isPathInside(skillDir, skillsDir)) {
|
|
@@ -250,7 +281,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
250
281
|
}
|
|
251
282
|
|
|
252
283
|
await requestCreationPermission(
|
|
253
|
-
|
|
284
|
+
authSessionID,
|
|
254
285
|
messageId,
|
|
255
286
|
callId
|
|
256
287
|
)
|
|
@@ -259,7 +290,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
259
290
|
method: "POST",
|
|
260
291
|
headers: {
|
|
261
292
|
"Content-Type": "application/json",
|
|
262
|
-
Authorization: `Bearer ${
|
|
293
|
+
Authorization: `Bearer ${authSessionID}`,
|
|
263
294
|
},
|
|
264
295
|
body: JSON.stringify({
|
|
265
296
|
name: slug,
|
|
@@ -278,7 +309,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
278
309
|
`${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}/files/content?path=${encodeURIComponent("SKILL.md")}`,
|
|
279
310
|
{
|
|
280
311
|
headers: {
|
|
281
|
-
Authorization: `Bearer ${
|
|
312
|
+
Authorization: `Bearer ${authSessionID}`,
|
|
282
313
|
},
|
|
283
314
|
}
|
|
284
315
|
)
|
|
@@ -302,7 +333,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
302
333
|
method: "PUT",
|
|
303
334
|
headers: {
|
|
304
335
|
"Content-Type": "application/json",
|
|
305
|
-
Authorization: `Bearer ${
|
|
336
|
+
Authorization: `Bearer ${authSessionID}`,
|
|
306
337
|
},
|
|
307
338
|
body: JSON.stringify({
|
|
308
339
|
path: "SKILL.md",
|
|
@@ -324,7 +355,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
|
|
|
324
355
|
skill: {
|
|
325
356
|
slug,
|
|
326
357
|
description,
|
|
327
|
-
file_path:
|
|
358
|
+
file_path: `.agents/skills/${slug}/SKILL.md`,
|
|
328
359
|
},
|
|
329
360
|
},
|
|
330
361
|
null,
|
|
@@ -37,6 +37,14 @@ interface PermissionResponse {
|
|
|
37
37
|
approved: boolean
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
interface SessionLookupResponse {
|
|
41
|
+
error?: unknown
|
|
42
|
+
data?: {
|
|
43
|
+
id?: string
|
|
44
|
+
parentID?: string
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
// ============================================================================
|
|
41
49
|
// Constants
|
|
42
50
|
// ============================================================================
|
|
@@ -195,7 +203,29 @@ async function requestWorkflowPermission(
|
|
|
195
203
|
// Plugin
|
|
196
204
|
// ============================================================================
|
|
197
205
|
|
|
198
|
-
export const CreateWorkflowPlugin: Plugin = async (
|
|
206
|
+
export const CreateWorkflowPlugin: Plugin = async ({ client }) => {
|
|
207
|
+
async function resolveRootSessionID(sessionID: string): Promise<string> {
|
|
208
|
+
let currentSessionID = sessionID
|
|
209
|
+
|
|
210
|
+
while (true) {
|
|
211
|
+
const response = (await client.session.get({
|
|
212
|
+
path: {
|
|
213
|
+
id: currentSessionID,
|
|
214
|
+
},
|
|
215
|
+
})) as SessionLookupResponse
|
|
216
|
+
if (response.error) {
|
|
217
|
+
throw new Error(`Failed to resolve root session for ${currentSessionID}`)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const parentID = response.data?.parentID
|
|
221
|
+
if (!parentID) {
|
|
222
|
+
return currentSessionID
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
currentSessionID = parentID
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
199
229
|
return {
|
|
200
230
|
tool: {
|
|
201
231
|
createWorkflow: tool({
|
|
@@ -237,6 +267,7 @@ export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
|
|
|
237
267
|
},
|
|
238
268
|
async execute(args, context) {
|
|
239
269
|
const { directory, sessionID } = context
|
|
270
|
+
const authSessionID = await resolveRootSessionID(sessionID)
|
|
240
271
|
const { slug, intent_summary, inputs = {}, timeout_seconds = 300 } = args
|
|
241
272
|
const messageId = extractMessageId(context)
|
|
242
273
|
const callId = extractCallId(context)
|
|
@@ -278,7 +309,7 @@ export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
|
|
|
278
309
|
|
|
279
310
|
// Request permission after basic validation and existence checks pass.
|
|
280
311
|
await requestWorkflowPermission(
|
|
281
|
-
|
|
312
|
+
authSessionID,
|
|
282
313
|
messageId,
|
|
283
314
|
callId
|
|
284
315
|
)
|
|
@@ -316,7 +347,7 @@ export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
|
|
|
316
347
|
method: "POST",
|
|
317
348
|
headers: {
|
|
318
349
|
"Content-Type": "application/json",
|
|
319
|
-
Authorization: `Bearer ${
|
|
350
|
+
Authorization: `Bearer ${authSessionID}`,
|
|
320
351
|
},
|
|
321
352
|
}
|
|
322
353
|
)
|