@agent-native/core 0.203.2-nightly-20261007213109 → 0.204.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/a2a/handlers.js +57 -1
- package/dist/a2a/server.js +37 -1
- package/dist/action.d.ts +1 -0
- package/dist/action.js +26 -3
- package/dist/agent/contain-principal.d.ts +9 -0
- package/dist/agent/contain-principal.js +53 -0
- package/dist/agent/production-agent.js +32 -2
- package/dist/agent/run-manager.js +14 -2
- package/dist/agent/run-store.js +66 -12
- package/dist/audit/actions/export-audit-events.d.ts +1 -1
- package/dist/audit/actions/export-audit-events.js +2 -2
- package/dist/audit/actions/export-audit-ocsf.d.ts +11 -0
- package/dist/audit/actions/export-audit-ocsf.js +183 -0
- package/dist/audit/actions/list-audit-events.d.ts +1 -1
- package/dist/audit/actions/list-audit-events.js +3 -3
- package/dist/audit/config.d.ts +1 -1
- package/dist/audit/config.js +5 -1
- package/dist/audit/index.d.ts +1 -0
- package/dist/audit/index.js +1 -0
- package/dist/audit/ocsf.d.ts +70 -0
- package/dist/audit/ocsf.js +228 -0
- package/dist/audit/record.js +1 -1
- package/dist/audit/store.js +9 -2
- package/dist/audit/types.d.ts +12 -1
- package/dist/cli/agent.js +2 -11
- package/dist/cli/connect.js +7 -2
- package/dist/client/use-db-sync.d.ts +13 -0
- package/dist/client/use-db-sync.js +22 -1
- package/dist/coding-tools/run-code.js +22 -1
- package/dist/coding-tools/sandbox/local-child-process-adapter.js +20 -0
- package/dist/collab/client.js +36 -3
- package/dist/collab/routes.d.ts +9 -4
- package/dist/collab/routes.js +15 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +15 -8
- package/dist/deploy/workspace-deploy.js +88 -13
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/builder.js +18 -4
- package/dist/framework-tools.js +3 -0
- package/dist/identity/rekey.js +6 -0
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/localization/core-messages/supplemental/ar-SA.js +15 -0
- package/dist/localization/core-messages/supplemental/de-DE.js +15 -0
- package/dist/localization/core-messages/supplemental/en-US.js +15 -0
- package/dist/localization/core-messages/supplemental/es-ES.js +15 -0
- package/dist/localization/core-messages/supplemental/fr-FR.js +15 -0
- package/dist/localization/core-messages/supplemental/hi-IN.js +15 -0
- package/dist/localization/core-messages/supplemental/ja-JP.js +15 -0
- package/dist/localization/core-messages/supplemental/ko-KR.js +15 -0
- package/dist/localization/core-messages/supplemental/pt-BR.js +15 -0
- package/dist/localization/core-messages/supplemental/zh-CN.js +15 -0
- package/dist/localization/core-messages/supplemental/zh-TW.js +15 -0
- package/dist/localization/mcp-settings-messages.d.ts +15 -0
- package/dist/mcp/actions/create-org-service-token.d.ts +8 -0
- package/dist/mcp/actions/create-org-service-token.js +93 -4
- package/dist/mcp/actions/list-org-service-tokens.d.ts +2 -0
- package/dist/mcp/actions/list-org-service-tokens.js +36 -8
- package/dist/mcp/actions/service-principal-input.d.ts +48 -0
- package/dist/mcp/actions/service-principal-input.js +102 -0
- package/dist/mcp/actions/set-service-principal-lifecycle.d.ts +15 -0
- package/dist/mcp/actions/set-service-principal-lifecycle.js +150 -0
- package/dist/mcp/actions/set-service-principal-policy.d.ts +14 -0
- package/dist/mcp/actions/set-service-principal-policy.js +105 -0
- package/dist/mcp/build-server.js +117 -13
- package/dist/mcp/connect-route.js +116 -4
- package/dist/mcp/connect-store.d.ts +7 -0
- package/dist/mcp/connect-store.js +21 -14
- package/dist/mcp-client/oauth-routes.js +2 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +7 -7
- package/dist/org/index.d.ts +2 -2
- package/dist/org/service-principal-guard.d.ts +64 -0
- package/dist/org/service-principal-guard.js +140 -0
- package/dist/org/service-principal-policy.d.ts +104 -0
- package/dist/org/service-principal-policy.js +272 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/scripts/dev/index.js +2 -8
- package/dist/scripts/parse-args.d.ts +7 -0
- package/dist/scripts/parse-args.js +76 -0
- package/dist/scripts/utils.d.ts +1 -1
- package/dist/scripts/utils.js +1 -1
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/action-discovery.js +16 -42
- package/dist/server/agent-chat/script-entries.js +3 -8
- package/dist/server/agent-chat-plugin.js +8 -42
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/auth.js +5 -2
- package/dist/server/core-routes-plugin.js +25 -6
- package/dist/server/google-oauth.js +8 -4
- package/dist/server/identity-sso.js +2 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/poll.d.ts +9 -0
- package/dist/server/poll.js +86 -7
- package/dist/server/query-echo-safe-redirect.d.ts +10 -0
- package/dist/server/query-echo-safe-redirect.js +102 -0
- package/dist/server/release-schema.js +4 -0
- package/dist/server/workspace-provider-oauth.js +2 -1
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -0
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -0
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +24 -25
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +33 -0
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -0
- package/dist/triggers/actions/manage-automation.d.ts +7 -7
- package/dist/vite/client.js +1 -0
- package/docs/content/audit-log.mdx +15 -0
- package/docs/content/external-agents-catalog.mdx +19 -0
- package/docs/content/http-api.mdx +21 -2
- package/docs/content/pr-visual-recap.mdx +3 -1
- package/docs/content/template-clips-embed.mdx +1 -1
- package/docs/content/turn-into-app.mdx +118 -0
- package/package.json +4 -4
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -0
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -0
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +24 -25
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +33 -0
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -0
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance record for an org service principal (`svc-<name>@service.<orgId>`).
|
|
3
|
+
*
|
|
4
|
+
* A service token proves WHO is calling; this record says who is ACCOUNTABLE
|
|
5
|
+
* for the principal, whether it may run at all, and which actions it may call.
|
|
6
|
+
* Every entry point that admits a service identity (MCP admission, MCP tool
|
|
7
|
+
* dispatch, agent run start) reads it through `evaluateServicePrincipal` so
|
|
8
|
+
* suspend and the action grant are enforced in one place.
|
|
9
|
+
*
|
|
10
|
+
* Constraints that must survive future edits:
|
|
11
|
+
*
|
|
12
|
+
* 1. No row means a legacy principal minted before governance existed: it
|
|
13
|
+
* stays active and unrestricted ("ungoverned"). Tightening it is an explicit
|
|
14
|
+
* admin act, never a side effect of upgrading.
|
|
15
|
+
* 2. An unreadable row is NOT an absent row. A read failure resolves to
|
|
16
|
+
* "unavailable" and callers refuse the request (503), because answering
|
|
17
|
+
* "ungoverned" would let a suspended principal back in during an outage.
|
|
18
|
+
* 3. A grant is deny-by-default once `allowedActions` is a list: an empty list
|
|
19
|
+
* grants nothing, and `null` is the only unrestricted value.
|
|
20
|
+
*/
|
|
21
|
+
import { getDbExec, withDbExec } from "../db/client.js";
|
|
22
|
+
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
23
|
+
import { parseServiceIdentityEmail } from "./service-identity.js";
|
|
24
|
+
export const SERVICE_PRINCIPAL_LIFECYCLES = [
|
|
25
|
+
"active",
|
|
26
|
+
"suspended",
|
|
27
|
+
"retired",
|
|
28
|
+
];
|
|
29
|
+
export const SERVICE_PRINCIPAL_RISK_TIERS = ["low", "medium", "high"];
|
|
30
|
+
export const MAX_SERVICE_PRINCIPAL_ACTIONS = 200;
|
|
31
|
+
const SERVICE_PRINCIPAL_ACTION_PATTERN = /^[A-Za-z0-9_.:-]+\*?$/;
|
|
32
|
+
export function isServicePrincipalActionPattern(value) {
|
|
33
|
+
return value.length <= 128 && SERVICE_PRINCIPAL_ACTION_PATTERN.test(value);
|
|
34
|
+
}
|
|
35
|
+
let _initPromise;
|
|
36
|
+
export async function ensureTable() {
|
|
37
|
+
if (!_initPromise) {
|
|
38
|
+
_initPromise = ensureTableExists("service_principal_policies", `CREATE TABLE IF NOT EXISTS service_principal_policies (
|
|
39
|
+
org_id TEXT NOT NULL,
|
|
40
|
+
service_name TEXT NOT NULL,
|
|
41
|
+
owner_email TEXT,
|
|
42
|
+
team TEXT,
|
|
43
|
+
risk_tier TEXT NOT NULL DEFAULT 'medium',
|
|
44
|
+
purpose TEXT,
|
|
45
|
+
lifecycle TEXT NOT NULL DEFAULT 'active',
|
|
46
|
+
allowed_actions TEXT,
|
|
47
|
+
lifecycle_reason TEXT,
|
|
48
|
+
lifecycle_changed_by TEXT,
|
|
49
|
+
lifecycle_changed_at BIGINT,
|
|
50
|
+
created_at BIGINT,
|
|
51
|
+
updated_at BIGINT,
|
|
52
|
+
PRIMARY KEY (org_id, service_name)
|
|
53
|
+
)`)
|
|
54
|
+
.then(() => undefined)
|
|
55
|
+
.catch((error) => {
|
|
56
|
+
_initPromise = undefined;
|
|
57
|
+
throw error;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return _initPromise;
|
|
61
|
+
}
|
|
62
|
+
function numOrNull(value) {
|
|
63
|
+
if (value == null)
|
|
64
|
+
return null;
|
|
65
|
+
const n = Number(value);
|
|
66
|
+
return Number.isFinite(n) ? n : null;
|
|
67
|
+
}
|
|
68
|
+
function parseAllowedActions(raw) {
|
|
69
|
+
if (raw == null)
|
|
70
|
+
return null;
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
74
|
+
// coercion-ok: an unparseable grant fails closed to an empty (deny-all) list, never to unrestricted.
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// A stored grant we cannot parse must not widen to "unrestricted".
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(parsed) ||
|
|
81
|
+
parsed.length > MAX_SERVICE_PRINCIPAL_ACTIONS ||
|
|
82
|
+
!parsed.every((item) => typeof item === "string" && isServicePrincipalActionPattern(item))) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
function mapRow(r) {
|
|
88
|
+
const lifecycle = String(r.lifecycle ?? "active");
|
|
89
|
+
const riskTier = String(r.risk_tier ?? r.riskTier ?? "medium");
|
|
90
|
+
return {
|
|
91
|
+
orgId: String(r.org_id ?? r.orgId),
|
|
92
|
+
serviceName: String(r.service_name ?? r.serviceName),
|
|
93
|
+
ownerEmail: (r.owner_email ?? r.ownerEmail ?? null),
|
|
94
|
+
team: (r.team ?? null),
|
|
95
|
+
riskTier: SERVICE_PRINCIPAL_RISK_TIERS.includes(riskTier)
|
|
96
|
+
? riskTier
|
|
97
|
+
: "high",
|
|
98
|
+
purpose: (r.purpose ?? null),
|
|
99
|
+
// An unrecognized lifecycle fails closed rather than reading as active.
|
|
100
|
+
lifecycle: SERVICE_PRINCIPAL_LIFECYCLES.includes(lifecycle)
|
|
101
|
+
? lifecycle
|
|
102
|
+
: "suspended",
|
|
103
|
+
allowedActions: parseAllowedActions(r.allowed_actions ?? r.allowedActions ?? null),
|
|
104
|
+
lifecycleReason: (r.lifecycle_reason ?? r.lifecycleReason ?? null),
|
|
105
|
+
lifecycleChangedBy: (r.lifecycle_changed_by ??
|
|
106
|
+
r.lifecycleChangedBy ??
|
|
107
|
+
null),
|
|
108
|
+
lifecycleChangedAt: numOrNull(r.lifecycle_changed_at ?? r.lifecycleChangedAt),
|
|
109
|
+
createdAt: numOrNull(r.created_at ?? r.createdAt),
|
|
110
|
+
updatedAt: numOrNull(r.updated_at ?? r.updatedAt),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const COLUMNS = `org_id, service_name, owner_email, team, risk_tier, purpose, lifecycle, allowed_actions, lifecycle_reason, lifecycle_changed_by, lifecycle_changed_at, created_at, updated_at`;
|
|
114
|
+
export function servicePrincipalLifecycleLockKey(orgId, serviceName) {
|
|
115
|
+
return `agent-native:service-principal-lifecycle:${orgId}:${serviceName}`;
|
|
116
|
+
}
|
|
117
|
+
export async function lockServicePrincipalLifecycle(db, orgId, serviceName) {
|
|
118
|
+
await db.execute({
|
|
119
|
+
sql: "SELECT pg_advisory_xact_lock(hashtextextended(?, 0::bigint))",
|
|
120
|
+
args: [servicePrincipalLifecycleLockKey(orgId, serviceName)],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export async function withServicePrincipalLifecycleLock(orgId, serviceName, work) {
|
|
124
|
+
await ensureTable();
|
|
125
|
+
const db = getDbExec();
|
|
126
|
+
if (!db.transaction) {
|
|
127
|
+
throw new Error("Service principal lifecycle requires transaction support");
|
|
128
|
+
}
|
|
129
|
+
return db.transaction(async (tx) => {
|
|
130
|
+
return withDbExec(tx, async () => {
|
|
131
|
+
await lockServicePrincipalLifecycle(tx, orgId, serviceName);
|
|
132
|
+
return work(tx);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** Throws when the store cannot be read; `null` means there is no record. */
|
|
137
|
+
export async function getServicePrincipalPolicy(orgId, serviceName, db = getDbExec()) {
|
|
138
|
+
await ensureTable();
|
|
139
|
+
const { rows } = await db.execute({
|
|
140
|
+
sql: `SELECT ${COLUMNS} FROM service_principal_policies WHERE org_id = ? AND service_name = ? LIMIT 1`,
|
|
141
|
+
args: [orgId, serviceName],
|
|
142
|
+
});
|
|
143
|
+
return rows[0] ? mapRow(rows[0]) : null;
|
|
144
|
+
}
|
|
145
|
+
export async function listServicePrincipalPolicies(orgId) {
|
|
146
|
+
await ensureTable();
|
|
147
|
+
const { rows } = await getDbExec().execute({
|
|
148
|
+
sql: `SELECT ${COLUMNS} FROM service_principal_policies WHERE org_id = ? ORDER BY service_name`,
|
|
149
|
+
args: [orgId],
|
|
150
|
+
});
|
|
151
|
+
return rows.map(mapRow);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Create or update the governance fields. Fields left `undefined` keep their
|
|
155
|
+
* stored value; `null` clears one. Lifecycle changes go through
|
|
156
|
+
* `setServicePrincipalLifecycle` so they always carry an actor and a reason.
|
|
157
|
+
*/
|
|
158
|
+
export async function upsertServicePrincipalPolicy(orgId, serviceName, input) {
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
const updatedColumns = [
|
|
161
|
+
input.ownerEmail !== undefined
|
|
162
|
+
? "owner_email = EXCLUDED.owner_email"
|
|
163
|
+
: null,
|
|
164
|
+
input.team !== undefined ? "team = EXCLUDED.team" : null,
|
|
165
|
+
input.riskTier !== undefined ? "risk_tier = EXCLUDED.risk_tier" : null,
|
|
166
|
+
input.purpose !== undefined ? "purpose = EXCLUDED.purpose" : null,
|
|
167
|
+
input.allowedActions !== undefined
|
|
168
|
+
? "allowed_actions = EXCLUDED.allowed_actions"
|
|
169
|
+
: null,
|
|
170
|
+
"updated_at = EXCLUDED.updated_at",
|
|
171
|
+
].filter((column) => column !== null);
|
|
172
|
+
const allowedJson = input.allowedActions == null ? null : JSON.stringify(input.allowedActions);
|
|
173
|
+
await getDbExec().execute({
|
|
174
|
+
sql: `INSERT INTO service_principal_policies (org_id, service_name, owner_email, team, risk_tier, purpose, lifecycle, allowed_actions, created_at, updated_at)
|
|
175
|
+
VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, ?)
|
|
176
|
+
ON CONFLICT (org_id, service_name) DO UPDATE SET ${updatedColumns.join(", ")}`,
|
|
177
|
+
args: [
|
|
178
|
+
orgId,
|
|
179
|
+
serviceName,
|
|
180
|
+
input.ownerEmail ?? null,
|
|
181
|
+
input.team ?? null,
|
|
182
|
+
input.riskTier ?? "medium",
|
|
183
|
+
input.purpose ?? null,
|
|
184
|
+
allowedJson,
|
|
185
|
+
now,
|
|
186
|
+
now,
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
const saved = await getServicePrincipalPolicy(orgId, serviceName);
|
|
190
|
+
if (!saved) {
|
|
191
|
+
throw new Error("Service principal policy was not readable after write.");
|
|
192
|
+
}
|
|
193
|
+
return saved;
|
|
194
|
+
}
|
|
195
|
+
export class ServicePrincipalRetiredError extends Error {
|
|
196
|
+
statusCode = 409;
|
|
197
|
+
constructor() {
|
|
198
|
+
super("A retired service principal cannot be resumed.");
|
|
199
|
+
this.name = "ServicePrincipalRetiredError";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export async function setServicePrincipalLifecycle(orgId, serviceName, lifecycle, change, db = getDbExec()) {
|
|
203
|
+
await ensureTable();
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
// Upsert so a legacy principal with no record can be suspended in one step.
|
|
206
|
+
await db.execute({
|
|
207
|
+
sql: `INSERT INTO service_principal_policies (org_id, service_name, risk_tier, lifecycle, lifecycle_reason, lifecycle_changed_by, lifecycle_changed_at, created_at, updated_at) VALUES (?, ?, 'medium', ?, ?, ?, ?, ?, ?)
|
|
208
|
+
ON CONFLICT (org_id, service_name) DO UPDATE SET lifecycle = EXCLUDED.lifecycle, lifecycle_reason = EXCLUDED.lifecycle_reason, lifecycle_changed_by = EXCLUDED.lifecycle_changed_by, lifecycle_changed_at = EXCLUDED.lifecycle_changed_at, updated_at = EXCLUDED.updated_at
|
|
209
|
+
WHERE service_principal_policies.lifecycle != 'retired' OR EXCLUDED.lifecycle = 'retired'`,
|
|
210
|
+
args: [
|
|
211
|
+
orgId,
|
|
212
|
+
serviceName,
|
|
213
|
+
lifecycle,
|
|
214
|
+
change.reason ?? null,
|
|
215
|
+
change.actorEmail,
|
|
216
|
+
now,
|
|
217
|
+
now,
|
|
218
|
+
now,
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
const saved = await getServicePrincipalPolicy(orgId, serviceName, db);
|
|
222
|
+
if (!saved) {
|
|
223
|
+
throw new Error("Service principal policy was not readable after write.");
|
|
224
|
+
}
|
|
225
|
+
if (saved.lifecycle === "retired" && lifecycle !== "retired") {
|
|
226
|
+
throw new ServicePrincipalRetiredError();
|
|
227
|
+
}
|
|
228
|
+
return saved;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Resolve whether a caller may run from its identity email and verified org.
|
|
232
|
+
* Non-service identities return `not-service` without touching the database,
|
|
233
|
+
* so the hot path for human callers is unchanged.
|
|
234
|
+
*/
|
|
235
|
+
export async function evaluateServicePrincipal(email, orgId, db = getDbExec()) {
|
|
236
|
+
const parsed = parseServiceIdentityEmail(email);
|
|
237
|
+
if (!parsed)
|
|
238
|
+
return { status: "not-service" };
|
|
239
|
+
// The email alone is not proof of the org (see service-identity.ts); require
|
|
240
|
+
// a verified org claim before reading this principal's policy.
|
|
241
|
+
if (!orgId || orgId !== parsed.orgId)
|
|
242
|
+
return { status: "org-mismatch" };
|
|
243
|
+
try {
|
|
244
|
+
const policy = await getServicePrincipalPolicy(parsed.orgId, parsed.serviceName, db);
|
|
245
|
+
if (!policy) {
|
|
246
|
+
return {
|
|
247
|
+
status: "ungoverned",
|
|
248
|
+
orgId: parsed.orgId,
|
|
249
|
+
serviceName: parsed.serviceName,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return policy.lifecycle === "active"
|
|
253
|
+
? { status: "active", policy }
|
|
254
|
+
: { status: policy.lifecycle, policy };
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.error("[service-principal] Policy lookup failed:", error);
|
|
258
|
+
return { status: "unavailable" };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Whether the principal may call `actionName`. `null` grants everything. */
|
|
262
|
+
export function isActionGranted(allowedActions, actionName) {
|
|
263
|
+
if (allowedActions === null)
|
|
264
|
+
return true;
|
|
265
|
+
if (!allowedActions.every(isServicePrincipalActionPattern))
|
|
266
|
+
return false;
|
|
267
|
+
return allowedActions.some((pattern) => pattern.endsWith("*")
|
|
268
|
+
? actionName.startsWith(pattern.slice(0, -1))
|
|
269
|
+
: pattern === actionName);
|
|
270
|
+
}
|
|
271
|
+
/** Message returned to a refused caller; never echoes the stored reason. */
|
|
272
|
+
export const SERVICE_PRINCIPAL_SUSPENDED_MESSAGE = "This service principal is suspended or retired. Ask an organization admin to resume it.";
|
|
@@ -75,9 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
-
id?: undefined;
|
|
79
|
-
message?: undefined;
|
|
80
78
|
deleted?: undefined;
|
|
79
|
+
message?: undefined;
|
|
81
80
|
found?: undefined;
|
|
82
81
|
providers: {
|
|
83
82
|
id: string;
|
|
@@ -91,20 +90,21 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
91
90
|
count: number;
|
|
92
91
|
provider?: undefined;
|
|
93
92
|
registered?: undefined;
|
|
93
|
+
id?: undefined;
|
|
94
94
|
label?: undefined;
|
|
95
95
|
} | {
|
|
96
|
-
id?: undefined;
|
|
97
|
-
message?: undefined;
|
|
98
96
|
deleted?: undefined;
|
|
97
|
+
message?: undefined;
|
|
99
98
|
providers?: undefined;
|
|
100
99
|
count?: undefined;
|
|
101
100
|
found: boolean;
|
|
102
101
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
103
102
|
registered?: undefined;
|
|
103
|
+
id?: undefined;
|
|
104
104
|
label?: undefined;
|
|
105
105
|
} | {
|
|
106
|
-
message?: undefined;
|
|
107
106
|
deleted?: undefined;
|
|
107
|
+
message?: undefined;
|
|
108
108
|
providers?: undefined;
|
|
109
109
|
count?: undefined;
|
|
110
110
|
provider?: undefined;
|
|
@@ -311,8 +311,10 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
311
311
|
notes?: string | undefined;
|
|
312
312
|
scope?: "org" | "user" | undefined;
|
|
313
313
|
}, {
|
|
314
|
-
provider?: undefined;
|
|
315
314
|
id?: undefined;
|
|
315
|
+
deleted?: undefined;
|
|
316
|
+
provider?: undefined;
|
|
317
|
+
message?: undefined;
|
|
316
318
|
found?: undefined;
|
|
317
319
|
providers: {
|
|
318
320
|
id: string;
|
|
@@ -324,46 +326,44 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
324
326
|
updatedAt: number;
|
|
325
327
|
}[];
|
|
326
328
|
count: number;
|
|
327
|
-
deleted?: undefined;
|
|
328
329
|
registered?: undefined;
|
|
329
330
|
label?: undefined;
|
|
330
|
-
message?: undefined;
|
|
331
331
|
} | {
|
|
332
332
|
id?: undefined;
|
|
333
|
-
|
|
333
|
+
deleted?: undefined;
|
|
334
|
+
message?: undefined;
|
|
334
335
|
providers?: undefined;
|
|
336
|
+
count?: undefined;
|
|
335
337
|
found: boolean;
|
|
336
338
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
337
|
-
deleted?: undefined;
|
|
338
339
|
registered?: undefined;
|
|
339
340
|
label?: undefined;
|
|
340
|
-
message?: undefined;
|
|
341
341
|
} | {
|
|
342
|
+
deleted?: undefined;
|
|
342
343
|
provider?: undefined;
|
|
343
|
-
|
|
344
|
+
message?: undefined;
|
|
344
345
|
providers?: undefined;
|
|
346
|
+
count?: undefined;
|
|
345
347
|
found: boolean;
|
|
346
348
|
id: string;
|
|
347
|
-
deleted?: undefined;
|
|
348
349
|
registered?: undefined;
|
|
349
350
|
label?: undefined;
|
|
350
|
-
message?: undefined;
|
|
351
351
|
} | {
|
|
352
352
|
provider?: undefined;
|
|
353
|
-
|
|
353
|
+
message?: undefined;
|
|
354
354
|
found?: undefined;
|
|
355
355
|
providers?: undefined;
|
|
356
|
+
count?: undefined;
|
|
356
357
|
deleted: boolean;
|
|
357
358
|
id: string;
|
|
358
359
|
registered?: undefined;
|
|
359
360
|
label?: undefined;
|
|
360
|
-
message?: undefined;
|
|
361
361
|
} | {
|
|
362
|
+
deleted?: undefined;
|
|
362
363
|
provider?: undefined;
|
|
363
|
-
count?: undefined;
|
|
364
364
|
found?: undefined;
|
|
365
365
|
providers?: undefined;
|
|
366
|
-
|
|
366
|
+
count?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
|
369
369
|
label: string;
|
|
@@ -2,6 +2,7 @@ import { createCodingToolRegistry } from "../../coding-tools/index.js";
|
|
|
2
2
|
import { getRequestRunContext } from "../../server/request-context.js";
|
|
3
3
|
import { normalizeDatabaseToolsMode, } from "../db/tool-mode.js";
|
|
4
4
|
import { dbExecToolParameters } from "../db/tool-schemas.js";
|
|
5
|
+
import { serializeCliArgs } from "../parse-args.js";
|
|
5
6
|
import { tool as listFilesTool, run as listFilesRun } from "./list-files.js";
|
|
6
7
|
import { tool as readFileTool, run as readFileRun } from "./read-file.js";
|
|
7
8
|
import { tool as searchFilesTool, run as searchFilesRun, } from "./search-files.js";
|
|
@@ -12,14 +13,7 @@ function wrapCliScript(tool, cliDefault, opts) {
|
|
|
12
13
|
tool,
|
|
13
14
|
...(opts?.readOnly ? { readOnly: true } : {}),
|
|
14
15
|
run: async (args) => {
|
|
15
|
-
const cliArgs =
|
|
16
|
-
for (const [k, v] of Object.entries(args)) {
|
|
17
|
-
const raw = v;
|
|
18
|
-
const value = raw != null && typeof raw === "object"
|
|
19
|
-
? JSON.stringify(raw)
|
|
20
|
-
: String(raw);
|
|
21
|
-
cliArgs.push(`--${k}`, value);
|
|
22
|
-
}
|
|
16
|
+
const cliArgs = serializeCliArgs(args);
|
|
23
17
|
const logs = [];
|
|
24
18
|
const origLog = console.log;
|
|
25
19
|
console.log = (...a) => {
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export declare function parseArgs(args: string[]): Record<string, string>;
|
|
2
|
+
export declare function serializeCliArgs(args: Record<string, unknown>): string[];
|
|
3
|
+
type NormalizeShellArgsOptions = {
|
|
4
|
+
backslashEscapes?: boolean;
|
|
5
|
+
splitAllWhitespace?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function normalizeShellArgs(input: string, options?: NormalizeShellArgsOptions): string[];
|
|
2
8
|
export declare function camelCaseArgs(args: Record<string, string>): Record<string, string>;
|
|
9
|
+
export {};
|
|
@@ -23,6 +23,82 @@ export function parseArgs(args) {
|
|
|
23
23
|
}
|
|
24
24
|
return result;
|
|
25
25
|
}
|
|
26
|
+
export function serializeCliArgs(args) {
|
|
27
|
+
return Object.entries(args).flatMap(([key, raw]) => {
|
|
28
|
+
const value = raw != null && typeof raw === "object"
|
|
29
|
+
? JSON.stringify(raw)
|
|
30
|
+
: String(raw);
|
|
31
|
+
return value.startsWith("--") ? [`--${key}=${value}`] : [`--${key}`, value];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function normalizeShellArgs(input, options = {}) {
|
|
35
|
+
const tokens = [];
|
|
36
|
+
let current = "";
|
|
37
|
+
let inDouble = false;
|
|
38
|
+
let inSingle = false;
|
|
39
|
+
let wasQuoted = false;
|
|
40
|
+
let escape = false;
|
|
41
|
+
const pushToken = () => {
|
|
42
|
+
tokens.push({ value: current, quoted: wasQuoted });
|
|
43
|
+
current = "";
|
|
44
|
+
wasQuoted = false;
|
|
45
|
+
};
|
|
46
|
+
for (let i = 0; i < input.length; i++) {
|
|
47
|
+
const ch = input[i];
|
|
48
|
+
if (escape) {
|
|
49
|
+
current += ch;
|
|
50
|
+
escape = false;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (options.backslashEscapes && ch === "\\") {
|
|
54
|
+
if (inSingle) {
|
|
55
|
+
current += ch;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
escape = true;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "'" && !inDouble) {
|
|
63
|
+
inSingle = !inSingle;
|
|
64
|
+
wasQuoted = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ch === '"' && !inSingle) {
|
|
68
|
+
inDouble = !inDouble;
|
|
69
|
+
wasQuoted = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if ((options.splitAllWhitespace
|
|
73
|
+
? /\s/.test(ch)
|
|
74
|
+
: ch === " " || ch === "\t") &&
|
|
75
|
+
!inSingle &&
|
|
76
|
+
!inDouble) {
|
|
77
|
+
if (current.length > 0 || wasQuoted)
|
|
78
|
+
pushToken();
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
current += ch;
|
|
82
|
+
}
|
|
83
|
+
if (current.length > 0 || wasQuoted)
|
|
84
|
+
pushToken();
|
|
85
|
+
const normalized = [];
|
|
86
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
87
|
+
const token = tokens[i];
|
|
88
|
+
const next = tokens[i + 1];
|
|
89
|
+
if (token.value.startsWith("--") &&
|
|
90
|
+
!token.value.includes("=") &&
|
|
91
|
+
next?.quoted &&
|
|
92
|
+
next.value.startsWith("--")) {
|
|
93
|
+
normalized.push(`${token.value}=${next.value}`);
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
normalized.push(token.value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return normalized;
|
|
101
|
+
}
|
|
26
102
|
export function camelCaseArgs(args) {
|
|
27
103
|
const result = {};
|
|
28
104
|
for (const [key, value] of Object.entries(args)) {
|
package/dist/scripts/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { findWorkspaceRoot } from "./workspace-root.js";
|
|
2
2
|
export { fail, type FailOptions } from "../action.js";
|
|
3
|
-
export { parseArgs, camelCaseArgs } from "./parse-args.js";
|
|
3
|
+
export { parseArgs, serializeCliArgs, camelCaseArgs } from "./parse-args.js";
|
|
4
4
|
export declare function loadEnv(envPath?: string): void;
|
|
5
5
|
export declare function isValidPath(p: string): boolean;
|
|
6
6
|
export declare function isValidProjectPath(project: string): boolean;
|
package/dist/scripts/utils.js
CHANGED
|
@@ -4,7 +4,7 @@ import dotenv from "dotenv";
|
|
|
4
4
|
import { findWorkspaceRoot } from "./workspace-root.js";
|
|
5
5
|
export { findWorkspaceRoot } from "./workspace-root.js";
|
|
6
6
|
export { fail } from "../action.js";
|
|
7
|
-
export { parseArgs, camelCaseArgs } from "./parse-args.js";
|
|
7
|
+
export { parseArgs, serializeCliArgs, camelCaseArgs } from "./parse-args.js";
|
|
8
8
|
export function loadEnv(envPath) {
|
|
9
9
|
const appEnv = envPath ?? path.join(process.cwd(), ".env");
|
|
10
10
|
const shellKeys = new Set(Object.keys(process.env));
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -58,47 +58,47 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
58
58
|
error: string;
|
|
59
59
|
errorCode?: undefined;
|
|
60
60
|
retryable?: undefined;
|
|
61
|
-
ok?: undefined;
|
|
62
61
|
status?: undefined;
|
|
62
|
+
ok?: undefined;
|
|
63
63
|
} | {
|
|
64
64
|
error: string;
|
|
65
65
|
errorCode: string;
|
|
66
66
|
retryable?: undefined;
|
|
67
|
-
ok?: undefined;
|
|
68
67
|
status?: undefined;
|
|
68
|
+
ok?: undefined;
|
|
69
69
|
} | {
|
|
70
70
|
error: string;
|
|
71
71
|
errorCode: string;
|
|
72
72
|
retryable: boolean;
|
|
73
|
-
ok?: undefined;
|
|
74
73
|
status?: undefined;
|
|
74
|
+
ok?: undefined;
|
|
75
75
|
} | {
|
|
76
|
-
error?: undefined;
|
|
77
76
|
errorCode?: undefined;
|
|
78
77
|
retryable?: undefined;
|
|
79
78
|
ok: boolean;
|
|
80
79
|
status: string;
|
|
80
|
+
error?: undefined;
|
|
81
81
|
} | {
|
|
82
|
-
ok?: undefined;
|
|
83
82
|
error: string;
|
|
84
83
|
removed?: undefined;
|
|
84
|
+
ok?: undefined;
|
|
85
85
|
} | {
|
|
86
|
-
error?: undefined;
|
|
87
86
|
ok: boolean;
|
|
88
87
|
removed: boolean;
|
|
88
|
+
error?: undefined;
|
|
89
89
|
}>>;
|
|
90
90
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
91
|
-
ok?: undefined;
|
|
92
91
|
error: string;
|
|
93
92
|
note?: undefined;
|
|
93
|
+
ok?: undefined;
|
|
94
94
|
} | {
|
|
95
|
-
error?: undefined;
|
|
96
95
|
ok: boolean;
|
|
97
96
|
note?: undefined;
|
|
98
|
-
} | {
|
|
99
97
|
error?: undefined;
|
|
98
|
+
} | {
|
|
100
99
|
ok: boolean;
|
|
101
100
|
note: string;
|
|
101
|
+
error?: undefined;
|
|
102
102
|
} | {
|
|
103
103
|
note?: undefined;
|
|
104
104
|
ok: boolean;
|
|
@@ -126,11 +126,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
|
|
|
126
126
|
} | {
|
|
127
127
|
error: string;
|
|
128
128
|
} | {
|
|
129
|
-
error?: undefined;
|
|
130
129
|
ok: boolean;
|
|
131
130
|
key: string;
|
|
132
|
-
} | {
|
|
133
131
|
error?: undefined;
|
|
132
|
+
} | {
|
|
134
133
|
ok: boolean;
|
|
135
134
|
removed: boolean;
|
|
135
|
+
error?: undefined;
|
|
136
136
|
}>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
2
|
import "../authorization/check-action.js";
|
|
3
3
|
import { CORE_ACTION_GROUPS } from "../framework-tools.js";
|
|
4
|
+
import { normalizeShellArgs, serializeCliArgs } from "../scripts/parse-args.js";
|
|
4
5
|
import { captureCliOutput } from "./cli-capture.js";
|
|
5
6
|
let _fs;
|
|
6
7
|
async function getFs() {
|
|
@@ -54,39 +55,6 @@ export function mergePackageActions(registry) {
|
|
|
54
55
|
registry[name] = entry;
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
function splitShellArgs(input) {
|
|
58
|
-
const tokens = [];
|
|
59
|
-
let current = "";
|
|
60
|
-
let inDouble = false;
|
|
61
|
-
let inSingle = false;
|
|
62
|
-
let wasQuoted = false;
|
|
63
|
-
for (let i = 0; i < input.length; i++) {
|
|
64
|
-
const ch = input[i];
|
|
65
|
-
if (ch === '"' && !inSingle) {
|
|
66
|
-
inDouble = !inDouble;
|
|
67
|
-
wasQuoted = true;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (ch === "'" && !inDouble) {
|
|
71
|
-
inSingle = !inSingle;
|
|
72
|
-
wasQuoted = true;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if ((ch === " " || ch === "\t") && !inDouble && !inSingle) {
|
|
76
|
-
if (current.length > 0 || wasQuoted) {
|
|
77
|
-
tokens.push(current);
|
|
78
|
-
}
|
|
79
|
-
current = "";
|
|
80
|
-
wasQuoted = false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
current += ch;
|
|
84
|
-
}
|
|
85
|
-
if (current.length > 0 || wasQuoted) {
|
|
86
|
-
tokens.push(current);
|
|
87
|
-
}
|
|
88
|
-
return tokens;
|
|
89
|
-
}
|
|
90
58
|
function wrapDefaultExport(name, defaultFn) {
|
|
91
59
|
const tool = {
|
|
92
60
|
description: `Run the "${name}" action. Pass arguments as key-value pairs.`,
|
|
@@ -103,15 +71,9 @@ function wrapDefaultExport(name, defaultFn) {
|
|
|
103
71
|
return {
|
|
104
72
|
tool,
|
|
105
73
|
run: async (args) => {
|
|
106
|
-
const cliArgs =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
for (const [k, v] of Object.entries(args)) {
|
|
112
|
-
cliArgs.push(`--${k}`, v);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
74
|
+
const cliArgs = args.args && Object.keys(args).length === 1
|
|
75
|
+
? normalizeShellArgs(args.args)
|
|
76
|
+
: serializeCliArgs(args);
|
|
115
77
|
return captureCliOutput(() => defaultFn(cliArgs));
|
|
116
78
|
},
|
|
117
79
|
};
|
|
@@ -772,6 +734,10 @@ export async function mergeCoreSharingActions(registry) {
|
|
|
772
734
|
"export-audit-events",
|
|
773
735
|
() => import("../audit/actions/export-audit-events.js"),
|
|
774
736
|
],
|
|
737
|
+
[
|
|
738
|
+
"export-audit-ocsf",
|
|
739
|
+
() => import("../audit/actions/export-audit-ocsf.js"),
|
|
740
|
+
],
|
|
775
741
|
[
|
|
776
742
|
"export-resource-pack",
|
|
777
743
|
() => import("../resources/actions/export-resource-pack.js"),
|
|
@@ -908,6 +874,14 @@ export async function mergeCoreSharingActions(registry) {
|
|
|
908
874
|
"revoke-org-service-token",
|
|
909
875
|
() => import("../mcp/actions/revoke-org-service-token.js"),
|
|
910
876
|
],
|
|
877
|
+
[
|
|
878
|
+
"set-service-principal-policy",
|
|
879
|
+
() => import("../mcp/actions/set-service-principal-policy.js"),
|
|
880
|
+
],
|
|
881
|
+
[
|
|
882
|
+
"set-service-principal-lifecycle",
|
|
883
|
+
() => import("../mcp/actions/set-service-principal-lifecycle.js"),
|
|
884
|
+
],
|
|
911
885
|
["list-mcp-tools", () => import("../mcp/actions/list-mcp-tools.js")],
|
|
912
886
|
["call-mcp-tool", () => import("../mcp/actions/call-mcp-tool.js")],
|
|
913
887
|
];
|