@sema-agent/server 1.307.0 → 1.309.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/dist/capabilities/center-plugins.d.ts +63 -0
- package/dist/capabilities/center-plugins.js +163 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/http/server.js +1 -1
- package/dist/main.js +17 -0
- package/dist/orchestration/workflow-completion-inbox.js +1 -1
- package/dist/sema-registry.d.ts +3 -0
- package/package.json +6 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Logger } from "../observability/logger.js";
|
|
2
|
+
import { type LoadedSkill } from "./skills.js";
|
|
3
|
+
export declare const DEFAULT_PLUGIN_ALLOW_HOSTS: readonly string[];
|
|
4
|
+
export interface PluginDeclWire {
|
|
5
|
+
name?: unknown;
|
|
6
|
+
source?: {
|
|
7
|
+
kind?: unknown;
|
|
8
|
+
url?: unknown;
|
|
9
|
+
marketplace?: unknown;
|
|
10
|
+
};
|
|
11
|
+
version?: unknown;
|
|
12
|
+
sha?: unknown;
|
|
13
|
+
enabled?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export type PluginValidation = {
|
|
16
|
+
ok: true;
|
|
17
|
+
name: string;
|
|
18
|
+
url: string;
|
|
19
|
+
sha?: string;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
skip?: true;
|
|
23
|
+
name: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function validatePluginDecl(decl: PluginDeclWire, allowHosts: readonly string[]): PluginValidation;
|
|
27
|
+
export declare function gitClonePlan(o: {
|
|
28
|
+
url: string;
|
|
29
|
+
dest: string;
|
|
30
|
+
sha?: string;
|
|
31
|
+
}): {
|
|
32
|
+
steps: string[][];
|
|
33
|
+
env: Record<string, string>;
|
|
34
|
+
};
|
|
35
|
+
export declare function resolvePluginSkillsRoot(cloneRoot: string): {
|
|
36
|
+
ok: true;
|
|
37
|
+
dir?: string;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
reason: string;
|
|
41
|
+
};
|
|
42
|
+
export interface ApplyPluginsResult {
|
|
43
|
+
skills: LoadedSkill[];
|
|
44
|
+
failures: Array<{
|
|
45
|
+
plugin: string;
|
|
46
|
+
reason: string;
|
|
47
|
+
}>;
|
|
48
|
+
letGo: Array<{
|
|
49
|
+
plugin: string;
|
|
50
|
+
skill: string;
|
|
51
|
+
}>;
|
|
52
|
+
lkgUsed: string[];
|
|
53
|
+
}
|
|
54
|
+
export declare function applyCenterPlugins(baseline: LoadedSkill[], eff: {
|
|
55
|
+
plugins?: unknown;
|
|
56
|
+
}, opts: {
|
|
57
|
+
cacheRoot: string;
|
|
58
|
+
allowHosts: readonly string[];
|
|
59
|
+
logger?: Logger;
|
|
60
|
+
skipValidate?: boolean;
|
|
61
|
+
runGit?: (args: string[], env: Record<string, string>) => Promise<void>;
|
|
62
|
+
}): Promise<ApplyPluginsResult>;
|
|
63
|
+
//# sourceMappingURL=center-plugins.d.ts.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { promises as fsp, realpathSync, existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join, resolve, isAbsolute, sep } from "node:path";
|
|
5
|
+
import { loadSkills } from "./skills.js";
|
|
6
|
+
const execFileP = promisify(execFile);
|
|
7
|
+
export const DEFAULT_PLUGIN_ALLOW_HOSTS = ["github.com"];
|
|
8
|
+
export function validatePluginDecl(decl, allowHosts) {
|
|
9
|
+
const name = typeof decl.name === "string" && decl.name !== "" ? decl.name : "(unnamed)";
|
|
10
|
+
if (decl.enabled === false)
|
|
11
|
+
return { ok: false, skip: true, name, reason: "enabled:false" };
|
|
12
|
+
const kind = decl.source?.kind;
|
|
13
|
+
if (kind === "marketplace") {
|
|
14
|
+
return { ok: false, skip: true, name, reason: "marketplace source: not materialized in v1 (skills lane ships git-form first)" };
|
|
15
|
+
}
|
|
16
|
+
if (kind !== "git")
|
|
17
|
+
return { ok: false, name, reason: `unsupported source.kind ${String(kind)}` };
|
|
18
|
+
const url = decl.source?.url;
|
|
19
|
+
if (typeof url !== "string" || !/^https:\/\//i.test(url)) {
|
|
20
|
+
return { ok: false, name, reason: `plugin source must be an https:// URL (contract: https-only; got ${String(url).slice(0, 80)})` };
|
|
21
|
+
}
|
|
22
|
+
let host;
|
|
23
|
+
try {
|
|
24
|
+
host = new URL(url).hostname.toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { ok: false, name, reason: "https url does not parse" };
|
|
28
|
+
}
|
|
29
|
+
if (!allowHosts.some((h) => h.toLowerCase() === host)) {
|
|
30
|
+
return { ok: false, name, reason: `host ${host} not in PLUGINS_ALLOW_HOSTS (default github.com; extend with a comma-separated https mirror domain list)` };
|
|
31
|
+
}
|
|
32
|
+
if (decl.sha !== undefined) {
|
|
33
|
+
if (typeof decl.sha !== "string" || !/^[a-f0-9]{40}$/.test(decl.sha)) {
|
|
34
|
+
return { ok: false, name, reason: `sha must be a full 40-hex lowercase commit (got ${String(decl.sha).slice(0, 50)})` };
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, name, url, sha: decl.sha };
|
|
37
|
+
}
|
|
38
|
+
return { ok: true, name, url };
|
|
39
|
+
}
|
|
40
|
+
export function gitClonePlan(o) {
|
|
41
|
+
const env = {
|
|
42
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
43
|
+
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
44
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
45
|
+
};
|
|
46
|
+
if (o.sha === undefined) {
|
|
47
|
+
return { steps: [["clone", "--depth", "1", "--quiet", o.url, o.dest]], env };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
steps: [
|
|
51
|
+
["init", "--quiet", o.dest],
|
|
52
|
+
["-C", o.dest, "fetch", "--depth", "1", "--quiet", o.url, o.sha],
|
|
53
|
+
["-C", o.dest, "checkout", "--quiet", o.sha],
|
|
54
|
+
],
|
|
55
|
+
env,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function resolvePluginSkillsRoot(cloneRoot) {
|
|
59
|
+
let skillsPath = "skills";
|
|
60
|
+
try {
|
|
61
|
+
const manifest = JSON.parse(readFileSync(join(cloneRoot, ".claude-plugin", "plugin.json"), "utf8"));
|
|
62
|
+
if (typeof manifest.skillsPath === "string" && manifest.skillsPath !== "")
|
|
63
|
+
skillsPath = manifest.skillsPath;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
}
|
|
67
|
+
if (isAbsolute(skillsPath))
|
|
68
|
+
return { ok: false, reason: `skillsPath must be relative (got ${skillsPath})` };
|
|
69
|
+
const rootReal = realpathSync(cloneRoot);
|
|
70
|
+
const candidate = resolve(rootReal, skillsPath);
|
|
71
|
+
if (candidate !== rootReal && !candidate.startsWith(rootReal + sep)) {
|
|
72
|
+
return { ok: false, reason: `skillsPath escapes the plugin root (${skillsPath})` };
|
|
73
|
+
}
|
|
74
|
+
if (!existsSync(candidate))
|
|
75
|
+
return { ok: true };
|
|
76
|
+
const real = realpathSync(candidate);
|
|
77
|
+
if (real !== rootReal && !real.startsWith(rootReal + sep)) {
|
|
78
|
+
return { ok: false, reason: `skillsPath resolves outside the plugin root via symlink (${skillsPath})` };
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, dir: candidate };
|
|
81
|
+
}
|
|
82
|
+
export async function applyCenterPlugins(baseline, eff, opts) {
|
|
83
|
+
const declared = Array.isArray(eff.plugins)
|
|
84
|
+
? (eff.plugins)
|
|
85
|
+
: Array.isArray(eff.plugins?.plugins)
|
|
86
|
+
? (eff.plugins.plugins)
|
|
87
|
+
: [];
|
|
88
|
+
const out = { skills: [...baseline], failures: [], letGo: [], lkgUsed: [] };
|
|
89
|
+
if (declared.length === 0)
|
|
90
|
+
return out;
|
|
91
|
+
const runGit = opts.runGit ?? (async (args, env) => {
|
|
92
|
+
await execFileP("git", args, { env: { ...process.env, ...env }, timeout: 120_000, maxBuffer: 4 * 1024 * 1024 });
|
|
93
|
+
});
|
|
94
|
+
const taken = new Set(baseline.map((s) => s.spec.name));
|
|
95
|
+
for (const decl of declared) {
|
|
96
|
+
const v = opts.skipValidate
|
|
97
|
+
? { ok: true, name: String(decl.name), url: String(decl.source?.url), ...(typeof decl.sha === "string" ? { sha: decl.sha } : {}) }
|
|
98
|
+
: validatePluginDecl(decl, opts.allowHosts);
|
|
99
|
+
if (!v.ok) {
|
|
100
|
+
if (v.skip)
|
|
101
|
+
opts.logger?.info("center_plugin_skipped", { plugin: v.name, reason: v.reason });
|
|
102
|
+
else {
|
|
103
|
+
opts.logger?.warn("center_plugin_rejected", { plugin: v.name, reason: v.reason });
|
|
104
|
+
out.failures.push({ plugin: v.name, reason: v.reason });
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const liveDir = join(opts.cacheRoot, v.name, "live");
|
|
109
|
+
const staging = join(opts.cacheRoot, v.name, `staging-${process.pid}`);
|
|
110
|
+
let materialized = false;
|
|
111
|
+
try {
|
|
112
|
+
await fsp.rm(staging, { recursive: true, force: true });
|
|
113
|
+
await fsp.mkdir(staging, { recursive: true });
|
|
114
|
+
const plan = gitClonePlan({ url: v.url, dest: staging, sha: v.sha });
|
|
115
|
+
for (const step of plan.steps)
|
|
116
|
+
await runGit(step, plan.env);
|
|
117
|
+
await fsp.rm(liveDir, { recursive: true, force: true });
|
|
118
|
+
await fsp.mkdir(join(opts.cacheRoot, v.name), { recursive: true });
|
|
119
|
+
await fsp.rename(staging, liveDir);
|
|
120
|
+
materialized = true;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
await fsp.rm(staging, { recursive: true, force: true }).catch(() => undefined);
|
|
124
|
+
if (existsSync(liveDir)) {
|
|
125
|
+
opts.logger?.warn("center_plugin_fetch_failed_lkg", { plugin: v.name, error: err instanceof Error ? err.message : String(err) });
|
|
126
|
+
out.lkgUsed.push(v.name);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
opts.logger?.warn("center_plugin_fetch_failed", { plugin: v.name, error: err instanceof Error ? err.message : String(err) });
|
|
130
|
+
out.failures.push({ plugin: v.name, reason: `fetch failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const rootCheck = resolvePluginSkillsRoot(liveDir);
|
|
136
|
+
if (!rootCheck.ok) {
|
|
137
|
+
out.failures.push({ plugin: v.name, reason: rootCheck.reason });
|
|
138
|
+
opts.logger?.warn("center_plugin_manifest_rejected", { plugin: v.name, reason: rootCheck.reason });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!rootCheck.dir)
|
|
142
|
+
continue;
|
|
143
|
+
const loaded = loadSkills(rootCheck.dir);
|
|
144
|
+
for (const skill of loaded) {
|
|
145
|
+
if (taken.has(skill.spec.name)) {
|
|
146
|
+
out.letGo.push({ plugin: v.name, skill: skill.spec.name });
|
|
147
|
+
opts.logger?.warn("center_plugin_skill_shadowed", { plugin: v.name, skill: skill.spec.name, note: "name taken by builtin/center skill — plugin yields (contract tightening-4)" });
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
taken.add(skill.spec.name);
|
|
151
|
+
out.skills.push(skill);
|
|
152
|
+
}
|
|
153
|
+
if (materialized)
|
|
154
|
+
opts.logger?.info("center_plugin_loaded", { plugin: v.name, skills: loaded.length, pinned: v.sha !== undefined });
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
out.failures.push({ plugin: v.name, reason: `load failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
158
|
+
opts.logger?.warn("center_plugin_load_failed", { plugin: v.name, error: err instanceof Error ? err.message : String(err) });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=center-plugins.js.map
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -538,6 +538,7 @@ export function loadConfig() {
|
|
|
538
538
|
: undefined,
|
|
539
539
|
snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
|
|
540
540
|
snapshotBlobAllowSql: process.env.SNAPSHOT_BLOB_ALLOW_SQL_BYTES === "true",
|
|
541
|
+
pluginsAllowHosts: (process.env.PLUGINS_ALLOW_HOSTS ?? "github.com").split(",").map((h) => h.trim()).filter(Boolean),
|
|
541
542
|
bindHost: process.env.BIND_HOST || process.env.HOST || undefined,
|
|
542
543
|
attachmentOrphanGraceMs: ((v) => (v !== undefined && Number.isFinite(v) && v >= 0 ? v : 3_600_000))(process.env.ATTACHMENT_ORPHAN_GRACE_MS !== undefined ? Number(process.env.ATTACHMENT_ORPHAN_GRACE_MS) : undefined),
|
|
543
544
|
workspaceFileMaxBytes: optFinitePositiveEnv("WORKSPACE_FILE_MAX_BYTES") ?? 8 * 1024 * 1024,
|
package/dist/http/server.js
CHANGED
|
@@ -634,7 +634,7 @@ export function createHttpServer(deps) {
|
|
|
634
634
|
}
|
|
635
635
|
if (deps.drainState?.draining && req.method === "POST" && isBillableSubmitPath(url)) {
|
|
636
636
|
res.setHeader("retry-after", "15");
|
|
637
|
-
sendJson(res, 503, { error: "draining", message: "this instance is draining for shutdown/upgrade — retry against the replacement instance" });
|
|
637
|
+
sendJson(res, 503, { error: "draining", errorCode: "draining", message: "this instance is draining for shutdown/upgrade — retry against the replacement instance" });
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
640
|
if (deps.modelReady && !deps.modelReady() && req.method === "POST" && isBillableSubmitPath(url)) {
|
package/dist/main.js
CHANGED
|
@@ -46,6 +46,7 @@ import { buildSessionAuditPg, buildSessionAuditLocal, buildSessionAudit } from "
|
|
|
46
46
|
import { makeDegenerateInstrument } from "./degenerate-instrument.js";
|
|
47
47
|
import { PlanCacheProbe } from "./plan-cache-probe.js";
|
|
48
48
|
import { loadSkills } from "./capabilities/skills.js";
|
|
49
|
+
import { applyCenterPlugins } from "./capabilities/center-plugins.js";
|
|
49
50
|
import { GiteaClient } from "./capabilities/repo-tools.js";
|
|
50
51
|
import { buildScenarios, selectScenario, centerScenarios, mergeUserSkills, builtinScenarioDetails, centerScenarioDetails, gateScenarioRequest } from "./capabilities/scenarios.js";
|
|
51
52
|
import { applyLongtailDefer } from "./capabilities/tool-defer.js";
|
|
@@ -1270,6 +1271,22 @@ async function main() {
|
|
|
1270
1271
|
if (effective?.skills && config.configCenter) {
|
|
1271
1272
|
skills = await applyCenterSkills(skills, effective.skills, config.configCenter.baseUrl, config.configCenter.token, logger, undefined, process.env.CONFIG_LKG_DISABLED !== "true" ? defaultSkillCacheDir() : undefined);
|
|
1272
1273
|
}
|
|
1274
|
+
if (effective?.plugins && config.configCenter) {
|
|
1275
|
+
const pluginOut = await applyCenterPlugins(skills, effective, {
|
|
1276
|
+
cacheRoot: join(config.localDataRoot ?? join(homedir(), ".ai-agent"), "plugin-cache"),
|
|
1277
|
+
allowHosts: config.pluginsAllowHosts,
|
|
1278
|
+
logger,
|
|
1279
|
+
}).catch((err) => {
|
|
1280
|
+
logger.warn("center_plugins_apply_failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1281
|
+
return undefined;
|
|
1282
|
+
});
|
|
1283
|
+
if (pluginOut) {
|
|
1284
|
+
skills = pluginOut.skills;
|
|
1285
|
+
if (pluginOut.failures.length + pluginOut.letGo.length + pluginOut.lkgUsed.length > 0) {
|
|
1286
|
+
logger.info("center_plugins_applied", { failures: pluginOut.failures, letGo: pluginOut.letGo, lkgUsed: pluginOut.lkgUsed });
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1273
1290
|
if (bootLkgCandidate && lkgEnabled) {
|
|
1274
1291
|
const bootPublished = await persistLkgDurable(bootLkgCandidate.effective, bootLkgCandidate.etag).catch((err) => {
|
|
1275
1292
|
logger.warn("config_lkg_save_failed", { path: lkgPath, err: String(err), note: "boot LKG persist failed — retained for the per-tick retry" });
|
|
@@ -174,7 +174,7 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
174
174
|
closeSync(this.fd);
|
|
175
175
|
this.fd = undefined;
|
|
176
176
|
}
|
|
177
|
-
const tmp = `${this.ledgerPath}.tmp`;
|
|
177
|
+
const tmp = `${this.ledgerPath}.tmp.${process.pid}`;
|
|
178
178
|
const lines = [];
|
|
179
179
|
for (const q of this.bySession.values())
|
|
180
180
|
for (const entry of q.values())
|
package/dist/sema-registry.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.309.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
-
}
|
|
13
|
+
},
|
|
14
|
+
"./main": {
|
|
15
|
+
"default": "./dist/main.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
14
18
|
},
|
|
15
19
|
"files": [
|
|
16
20
|
"dist",
|