@opengeni/worker-bundle 1.0.0-canary.0 → 1.0.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/activities/agent-run-admission.d.ts +3 -1
- package/dist/activities/agent-turn/admission.d.ts +24 -3
- package/dist/activities/agent-turn/agent-build.d.ts +4 -0
- package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
- package/dist/activities/agent-turn/code-search.d.ts +60 -0
- package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
- package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
- package/dist/activities/agent-turn/codex.d.ts +3 -2
- package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
- package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
- package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
- package/dist/activities/agent-turn/errors.d.ts +82 -11
- package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
- package/dist/activities/agent-turn/final-reply.d.ts +18 -0
- package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
- package/dist/activities/agent-turn/finalization.d.ts +4 -2
- package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
- package/dist/activities/agent-turn/governance-model.d.ts +9 -2
- package/dist/activities/agent-turn/history-sink.d.ts +2 -0
- package/dist/activities/agent-turn/history.d.ts +2 -0
- package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
- package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
- package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
- package/dist/activities/agent-turn/model-usage.d.ts +6 -0
- package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
- package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
- package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
- package/dist/activities/agent-turn/quiescence.d.ts +7 -7
- package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
- package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
- package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
- package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
- package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
- package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
- package/dist/activities/agent-turn/session-title.d.ts +34 -2
- package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
- package/dist/activities/agent-turn/skill-read.d.ts +70 -2
- package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
- package/dist/activities/agent-turn/skill-search.d.ts +3 -1
- package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
- package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
- package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
- package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
- package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
- package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
- package/dist/activities/agent-turn/turn-context.d.ts +29 -0
- package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
- package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
- package/dist/activities/automations.d.ts +3 -1
- package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
- package/dist/activities/codemode-dispatcher.d.ts +9 -2
- package/dist/activities/codex-capacity.d.ts +18 -2
- package/dist/activities/codex-rotation.d.ts +7 -1
- package/dist/activities/context-compaction.d.ts +16 -2
- package/dist/activities/gmail-files.d.ts +32 -0
- package/dist/activities/goals.d.ts +8 -0
- package/dist/activities/knowledge-indexing.d.ts +19 -0
- package/dist/activities/mcp-account-routes.d.ts +3 -0
- package/dist/activities/mcp-credentials.d.ts +12 -13
- package/dist/activities/mcp-operation-authority.d.ts +0 -5
- package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
- package/dist/activities/open-suffix-resume.d.ts +13 -0
- package/dist/activities/parent-wake.d.ts +6 -12
- package/dist/activities/retained-screenshots.d.ts +7 -2
- package/dist/activities/rig-verification.d.ts +2 -3
- package/dist/activities/run-credential-renewal.d.ts +1 -0
- package/dist/activities/run-credentials.d.ts +15 -3
- package/dist/activities/sandbox-images.d.ts +1 -1
- package/dist/activities/sandbox-lease.d.ts +47 -9
- package/dist/activities/scheduled-tasks.d.ts +4 -35
- package/dist/activities/session-state.d.ts +70 -3
- package/dist/activities/types.d.ts +55 -6
- package/dist/activities/workspace-capture.d.ts +2 -0
- package/dist/activities/workspace-credential-provider.d.ts +46 -0
- package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
- package/dist/activities-control-BNPTRTOE.js.map +1 -0
- package/dist/activities-control.d.ts +182 -4
- package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
- package/dist/activities-turn-5LGZOSVU.js.map +1 -0
- package/dist/activities.d.ts +182 -4
- package/dist/artifact-materializer-entry.js +5 -2
- package/dist/artifact-materializer-entry.js.map +1 -1
- package/dist/artifact-outbox-entry.js +5 -2
- package/dist/artifact-outbox-entry.js.map +1 -1
- package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
- package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
- package/dist/chunk-3ZTRWDMN.js.map +1 -0
- package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
- package/dist/chunk-4ER3YXOT.js.map +1 -0
- package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
- package/dist/chunk-LJX7Z2KP.js.map +1 -0
- package/dist/editable-artifact-materializer-service.d.ts +2 -2
- package/dist/editable-artifact-outbox-service.d.ts +2 -2
- package/dist/host-export-pump.d.ts +3 -1
- package/dist/host-export.d.ts +1 -1
- package/dist/host-export.js +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +95 -3
- package/dist/index.js +137 -43
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +118 -8
- package/dist/retained-process-diagnostics.d.ts +4 -0
- package/dist/retained-process-retry.d.ts +7 -0
- package/dist/sandbox-resume.d.ts +55 -13
- package/dist/sandbox-routing.d.ts +5 -3
- package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
- package/dist/session-event-fanout.d.ts +6 -0
- package/dist/warm-capture-holder.d.ts +3 -0
- package/dist/worker-service-lifecycle.d.ts +12 -0
- package/dist/workflow-bundle.js +273 -41
- package/dist/workflows/activities.d.ts +1 -1
- package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
- package/dist/workflows/scheduled-tasks.d.ts +4 -0
- package/dist/workflows/session.d.ts +36 -0
- package/dist/workflows.d.ts +1 -0
- package/package.json +21 -19
- package/src/activities/agent-run-admission.ts +17 -1
- package/src/activities/agent-turn/admission.ts +66 -3
- package/src/activities/agent-turn/agent-build.ts +84 -10
- package/src/activities/agent-turn/claim.ts +186 -43
- package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
- package/src/activities/agent-turn/code-search.ts +275 -0
- package/src/activities/agent-turn/codex-capacity.ts +172 -8
- package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
- package/src/activities/agent-turn/codex.ts +13 -5
- package/src/activities/agent-turn/compaction-prep.ts +170 -102
- package/src/activities/agent-turn/credential-leases.ts +122 -216
- package/src/activities/agent-turn/credential-restriction.ts +14 -0
- package/src/activities/agent-turn/errors.ts +647 -48
- package/src/activities/agent-turn/failure-settlement.ts +682 -130
- package/src/activities/agent-turn/final-reply.ts +46 -0
- package/src/activities/agent-turn/finalization-monitor.ts +120 -0
- package/src/activities/agent-turn/finalization.ts +146 -43
- package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
- package/src/activities/agent-turn/governance-model.ts +69 -28
- package/src/activities/agent-turn/history-sink.ts +16 -2
- package/src/activities/agent-turn/history.ts +38 -11
- package/src/activities/agent-turn/input-wait-reply.ts +103 -0
- package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
- package/src/activities/agent-turn/media-artifacts.ts +2 -1
- package/src/activities/agent-turn/model-call-admission.ts +94 -0
- package/src/activities/agent-turn/model-usage.ts +64 -3
- package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
- package/src/activities/agent-turn/optional-repositories.ts +224 -0
- package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
- package/src/activities/agent-turn/quiescence.ts +10 -7
- package/src/activities/agent-turn/recovery-warning.ts +16 -0
- package/src/activities/agent-turn/refresh-credentials.ts +44 -0
- package/src/activities/agent-turn/run-credentials.ts +116 -55
- package/src/activities/agent-turn/run.ts +320 -93
- package/src/activities/agent-turn/sandbox-establish.ts +84 -8
- package/src/activities/agent-turn/sandbox-provision.ts +3 -1
- package/src/activities/agent-turn/sandbox-route.ts +4 -21
- package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
- package/src/activities/agent-turn/session-title.ts +81 -5
- package/src/activities/agent-turn/skill-checkout.ts +111 -24
- package/src/activities/agent-turn/skill-install.ts +1 -1
- package/src/activities/agent-turn/skill-read.ts +374 -28
- package/src/activities/agent-turn/skill-remove.ts +46 -0
- package/src/activities/agent-turn/skill-save.ts +1 -1
- package/src/activities/agent-turn/skill-search.ts +10 -1
- package/src/activities/agent-turn/skill-selection.ts +4 -0
- package/src/activities/agent-turn/skill-tools.ts +112 -18
- package/src/activities/agent-turn/skill-transfer.ts +68 -23
- package/src/activities/agent-turn/stream-attempt.ts +313 -77
- package/src/activities/agent-turn/subscription-lease.ts +115 -0
- package/src/activities/agent-turn/tool-environment.ts +316 -167
- package/src/activities/agent-turn/tool-policy.ts +80 -1
- package/src/activities/agent-turn/turn-context.ts +35 -0
- package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
- package/src/activities/agent-turn/xai-capacity.ts +94 -51
- package/src/activities/automations.ts +76 -4
- package/src/activities/browser-deadline-checkpoint.ts +279 -0
- package/src/activities/capabilities.ts +108 -11
- package/src/activities/codemode-dispatcher.ts +36 -8
- package/src/activities/codex-capacity.ts +60 -25
- package/src/activities/codex-rotation.ts +39 -17
- package/src/activities/context-compaction.ts +75 -18
- package/src/activities/gmail-files.ts +88 -0
- package/src/activities/goals.ts +116 -121
- package/src/activities/google-drive-publication.ts +1 -3
- package/src/activities/image-generation-operation.ts +1 -1
- package/src/activities/knowledge-indexing.ts +269 -15
- package/src/activities/knowledge-source-sync.ts +10 -0
- package/src/activities/mcp-account-routes.ts +17 -0
- package/src/activities/mcp-credentials.ts +83 -194
- package/src/activities/mcp-operation-authority.ts +16 -41
- package/src/activities/model-call-fact-reconciler.ts +112 -0
- package/src/activities/open-suffix-resume.ts +15 -2
- package/src/activities/parent-wake.ts +46 -21
- package/src/activities/retained-screenshots.ts +82 -11
- package/src/activities/rig-verification.ts +38 -37
- package/src/activities/run-credential-renewal.ts +13 -1
- package/src/activities/run-credentials.ts +59 -17
- package/src/activities/run-input.ts +25 -16
- package/src/activities/sandbox-images.ts +0 -1
- package/src/activities/sandbox-lease.ts +798 -82
- package/src/activities/scheduled-tasks.ts +550 -178
- package/src/activities/session-state.ts +245 -6
- package/src/activities/site-auth-maintenance.ts +3 -0
- package/src/activities/types.ts +72 -5
- package/src/activities/workflow-wake.ts +2 -17
- package/src/activities/workspace-capture.ts +13 -2
- package/src/activities/workspace-credential-provider.ts +283 -0
- package/src/activities-control.ts +4 -0
- package/src/activities-turn.ts +5 -0
- package/src/activities.ts +13 -0
- package/src/activity-services.ts +16 -4
- package/src/browser-deadline-checkpoint-contract.ts +5 -0
- package/src/editable-artifact-hint-broker.ts +3 -0
- package/src/editable-artifact-materializer-service.ts +7 -3
- package/src/editable-artifact-outbox-service.ts +4 -4
- package/src/github-rest-mcp.ts +1 -2
- package/src/host-export-pump.ts +26 -4
- package/src/host-export.ts +3 -0
- package/src/http.ts +4 -2
- package/src/index.ts +99 -37
- package/src/observability-metrics.ts +510 -5
- package/src/personal-github-git-credentials.ts +2 -1
- package/src/pr-review-credentials.ts +1 -3
- package/src/retained-process-diagnostics.ts +50 -0
- package/src/retained-process-retry.ts +36 -0
- package/src/sandbox-resume.ts +388 -101
- package/src/sandbox-routing.ts +150 -25
- package/src/sandbox-snapshot-diagnostics.ts +112 -0
- package/src/session-event-fanout.ts +23 -0
- package/src/warm-capture-holder.ts +15 -0
- package/src/worker-service-lifecycle.ts +53 -0
- package/src/workflows/activities.ts +16 -1
- package/src/workflows/browser-deadline-checkpoint.ts +50 -0
- package/src/workflows/file-upload-reaper.ts +2 -0
- package/src/workflows/scheduled-tasks.ts +26 -13
- package/src/workflows/session.ts +242 -22
- package/src/workflows.ts +4 -0
- package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
- package/dist/activities/packs.d.ts +0 -55
- package/dist/activities-control-HMFGSKC5.js.map +0 -1
- package/dist/activities-turn-AP67JADS.js.map +0 -1
- package/dist/chunk-A4XQWVZK.js.map +0 -1
- package/dist/chunk-CYITU5UH.js.map +0 -1
- package/dist/chunk-RAMNYSAW.js.map +0 -1
- package/src/activities/agent-turn/knowledge-sources.ts +0 -86
- package/src/activities/packs.ts +0 -166
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import type { AttemptToolDefinition } from "@opengeni/codemode";
|
|
2
|
-
import type
|
|
3
|
-
import type
|
|
4
|
-
import { checkoutSkillDirectory, readSkillDirectory } from "./skill-transfer";
|
|
2
|
+
import { SKILL_READ_MAX_PATHS, type SkillTextFile } from "@opengeni/runtime/skill-library";
|
|
3
|
+
import { checkoutSkillDirectory, readSkillDirectory, type SkillFileSystem } from "./skill-transfer";
|
|
5
4
|
import type { SkillSaveReceipt, SkillSaveRequest } from "./skill-save";
|
|
6
5
|
|
|
7
|
-
type
|
|
6
|
+
export type SkillCheckoutOutcome = "written" | "unchanged" | "refused" | "failed";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Content-free timing of one skill_checkout call. `resolve` covers authority
|
|
10
|
+
* and the Skill read, `sandbox` acquiring the turn's filesystem handle, and
|
|
11
|
+
* `write` the batched filesystem write. On a lazily provisioned turn whose
|
|
12
|
+
* first sandbox use is this checkout, acquiring the handle starts the box, so
|
|
13
|
+
* box start is inside `sandbox`. A phase that did not run is null.
|
|
14
|
+
*/
|
|
15
|
+
export type SkillCheckoutObservation = Readonly<{
|
|
16
|
+
outcome: SkillCheckoutOutcome;
|
|
17
|
+
selection: "all" | "paths";
|
|
18
|
+
files: number;
|
|
19
|
+
written: number;
|
|
20
|
+
unchanged: number;
|
|
21
|
+
resolveSeconds: number | null;
|
|
22
|
+
sandboxSeconds: number | null;
|
|
23
|
+
writeSeconds: number | null;
|
|
24
|
+
totalSeconds: number;
|
|
25
|
+
}>;
|
|
8
26
|
|
|
9
27
|
export function createSkillCheckoutAttemptToolDefinition(input: {
|
|
10
28
|
authorize: () => Promise<void>;
|
|
@@ -14,7 +32,9 @@ export function createSkillCheckoutAttemptToolDefinition(input: {
|
|
|
14
32
|
scopeVersion: number | null;
|
|
15
33
|
files: readonly SkillTextFile[];
|
|
16
34
|
}>;
|
|
17
|
-
filesystem: () => Promise<SkillFileSystem
|
|
35
|
+
filesystem: () => Promise<Pick<SkillFileSystem, "fsWriteFiles">>;
|
|
36
|
+
/** Telemetry only; a failure here never changes the result. */
|
|
37
|
+
observe?: (observation: SkillCheckoutObservation) => void;
|
|
18
38
|
}): AttemptToolDefinition {
|
|
19
39
|
return {
|
|
20
40
|
identity: { serverId: "opengeni", toolName: "skill_checkout" },
|
|
@@ -22,12 +42,19 @@ export function createSkillCheckoutAttemptToolDefinition(input: {
|
|
|
22
42
|
codemodePath: ["opengeni", "skill_checkout"],
|
|
23
43
|
title: "Check out Skill files",
|
|
24
44
|
description:
|
|
25
|
-
"Copy
|
|
45
|
+
"Copy Skill files into a sandbox-relative directory to run scripts or make larger edits. Starts the sandbox if needed. Writes all missing files in one step and never overwrites: files that already hold the same content are kept, and a different existing file fails the call, so repeating a checkout into the same directory is fast. Pass paths to copy only those files, for example one script to run. Only a complete copy that creates a new directory returns revisionId and scopeVersion for skill_publish. Does not publish changes. Use skill_read for reading without a sandbox.",
|
|
26
46
|
inputSchema: {
|
|
27
47
|
type: "object",
|
|
28
48
|
properties: {
|
|
29
49
|
skill: { type: "string", minLength: 1, maxLength: 512 },
|
|
30
50
|
directory: { type: "string", minLength: 1, maxLength: 1024 },
|
|
51
|
+
paths: {
|
|
52
|
+
type: "array",
|
|
53
|
+
minItems: 1,
|
|
54
|
+
maxItems: SKILL_READ_MAX_PATHS,
|
|
55
|
+
uniqueItems: true,
|
|
56
|
+
items: { type: "string", minLength: 1, maxLength: 1024 },
|
|
57
|
+
},
|
|
31
58
|
},
|
|
32
59
|
required: ["skill", "directory"],
|
|
33
60
|
additionalProperties: false,
|
|
@@ -35,37 +62,97 @@ export function createSkillCheckoutAttemptToolDefinition(input: {
|
|
|
35
62
|
annotations: {
|
|
36
63
|
readOnlyHint: false,
|
|
37
64
|
destructiveHint: false,
|
|
38
|
-
idempotentHint:
|
|
65
|
+
idempotentHint: true,
|
|
39
66
|
openWorldHint: false,
|
|
40
67
|
},
|
|
41
68
|
source: "opengeni",
|
|
42
69
|
approval: "none",
|
|
43
70
|
execute: async (args) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
if (
|
|
72
|
+
typeof args.skill !== "string" ||
|
|
73
|
+
typeof args.directory !== "string" ||
|
|
74
|
+
(args.paths !== undefined &&
|
|
75
|
+
(!Array.isArray(args.paths) || args.paths.some((path) => typeof path !== "string")))
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"skill_checkout requires a Skill, a directory, and optional relative paths.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const paths = args.paths as string[] | undefined;
|
|
82
|
+
const started = performance.now();
|
|
83
|
+
const phases: { resolve: number | null; sandbox: number | null; write: number | null } = {
|
|
84
|
+
resolve: null,
|
|
85
|
+
sandbox: null,
|
|
86
|
+
write: null,
|
|
56
87
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
const timed = async <T>(phase: keyof typeof phases, run: () => Promise<T>): Promise<T> => {
|
|
89
|
+
const phaseStarted = performance.now();
|
|
90
|
+
try {
|
|
91
|
+
return await run();
|
|
92
|
+
} finally {
|
|
93
|
+
phases[phase] = (performance.now() - phaseStarted) / 1_000;
|
|
94
|
+
}
|
|
61
95
|
};
|
|
96
|
+
let copied: Awaited<ReturnType<typeof checkoutSkillDirectory>> | undefined;
|
|
97
|
+
let outcome: SkillCheckoutOutcome = "refused";
|
|
98
|
+
try {
|
|
99
|
+
const skill = await timed("resolve", async () => {
|
|
100
|
+
await input.authorize();
|
|
101
|
+
return input.load(args.skill as string);
|
|
102
|
+
});
|
|
103
|
+
outcome = "failed";
|
|
104
|
+
const filesystem = await timed("sandbox", input.filesystem);
|
|
105
|
+
copied = await timed("write", () =>
|
|
106
|
+
checkoutSkillDirectory(
|
|
107
|
+
filesystem,
|
|
108
|
+
args.directory as string,
|
|
109
|
+
skill.files,
|
|
110
|
+
paths ? { paths } : {},
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
outcome = copied.written > 0 ? "written" : "unchanged";
|
|
114
|
+
// Only a complete copy into a directory this call created mirrors the
|
|
115
|
+
// revision exactly, so only it may serve as a skill_publish base.
|
|
116
|
+
const publishable = !paths && copied.createdDirectory;
|
|
117
|
+
const output = {
|
|
118
|
+
directory: copied.directory,
|
|
119
|
+
fileCount: copied.fileCount,
|
|
120
|
+
written: copied.written,
|
|
121
|
+
unchanged: copied.unchanged,
|
|
122
|
+
skillId: skill.skillId,
|
|
123
|
+
...(publishable
|
|
124
|
+
? { revisionId: skill.revisionId, scopeVersion: skill.scopeVersion }
|
|
125
|
+
: { publishable: false }),
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
isError: false,
|
|
129
|
+
content: [{ type: "text", text: JSON.stringify(output) }],
|
|
130
|
+
structuredContent: output,
|
|
131
|
+
};
|
|
132
|
+
} finally {
|
|
133
|
+
try {
|
|
134
|
+
input.observe?.({
|
|
135
|
+
outcome,
|
|
136
|
+
selection: paths ? "paths" : "all",
|
|
137
|
+
files: copied?.fileCount ?? 0,
|
|
138
|
+
written: copied?.written ?? 0,
|
|
139
|
+
unchanged: copied?.unchanged ?? 0,
|
|
140
|
+
resolveSeconds: phases.resolve,
|
|
141
|
+
sandboxSeconds: phases.sandbox,
|
|
142
|
+
writeSeconds: phases.write,
|
|
143
|
+
totalSeconds: (performance.now() - started) / 1_000,
|
|
144
|
+
});
|
|
145
|
+
} catch {
|
|
146
|
+
// Telemetry is best effort.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
62
149
|
},
|
|
63
150
|
};
|
|
64
151
|
}
|
|
65
152
|
|
|
66
153
|
export function createSkillPublishAttemptToolDefinition(input: {
|
|
67
154
|
authorize: () => Promise<void>;
|
|
68
|
-
filesystem: () => Promise<SkillFileSystem
|
|
155
|
+
filesystem: () => Promise<Pick<SkillFileSystem, "fsList" | "fsRead">>;
|
|
69
156
|
save: (request: SkillSaveRequest) => Promise<SkillSaveReceipt>;
|
|
70
157
|
}): AttemptToolDefinition {
|
|
71
158
|
return {
|
|
@@ -17,7 +17,7 @@ export function createSkillInstallAttemptToolDefinition(input: {
|
|
|
17
17
|
codemodePath: ["opengeni", "skill_install"],
|
|
18
18
|
title: "Install a Skill",
|
|
19
19
|
description:
|
|
20
|
-
"Install a Skill from a public skills.sh or GitHub URL, or library
|
|
20
|
+
"Install a Skill from a public skills.sh or GitHub URL, such as a public skill_search result, or a curated Opengeni library entry as library:<id>. The server resolves and pins the source; no prior preview or invented hash is required. The effective Skills setting governs agent installation: Automatic publishes, Review first leaves a pending change in Knowledge > Needs review while the task continues, and Off prevents agent installation. Human installation from the UI remains available. Do not ask an approval question for a pending receipt. Replacing an existing installation requires its current installation version, and preserves workspace customizations.",
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: "object",
|
|
23
23
|
properties: {
|
|
@@ -1,26 +1,183 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { boundModelToolOutputItem } from "@opengeni/codex";
|
|
2
|
+
import type { AttemptToolDefinition, AttemptToolExecutionContext } from "@opengeni/codemode";
|
|
3
|
+
import {
|
|
4
|
+
SKILL_USE_META_KEY,
|
|
5
|
+
SkillUse,
|
|
6
|
+
type AttemptToolResult,
|
|
7
|
+
type SkillReadKind,
|
|
8
|
+
type SkillUseSource,
|
|
9
|
+
} from "@opengeni/contracts";
|
|
10
|
+
import { modelToolResultFits } from "@opengeni/runtime";
|
|
2
11
|
import {
|
|
3
12
|
listSkillPaths,
|
|
4
13
|
PORTABLE_SKILL_MAX_FILES,
|
|
5
14
|
readSkillFiles,
|
|
15
|
+
skillScriptIndex,
|
|
6
16
|
SKILL_READ_MAX_PATHS,
|
|
7
17
|
type SkillTextFile,
|
|
8
18
|
} from "@opengeni/runtime/skill-library";
|
|
9
19
|
|
|
10
20
|
export const SKILL_READ_TOOL_NAME = "skill_read";
|
|
11
21
|
|
|
22
|
+
type JsonValue = NonNullable<AttemptToolResult["_meta"]>[string];
|
|
23
|
+
|
|
24
|
+
/** Content-free provenance of a resolved Skill. The caller never receives it. */
|
|
25
|
+
export type SkillReadOrigin = Readonly<{
|
|
26
|
+
id: string;
|
|
27
|
+
source: SkillUseSource;
|
|
28
|
+
/** Platform display label of a built-in; never user-authored text. */
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Whole-artifact digest, for an artifact without a ledger revision. */
|
|
31
|
+
contentSha256?: string;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
12
34
|
export type SkillReadContent = Readonly<{
|
|
13
35
|
files: readonly SkillTextFile[];
|
|
14
36
|
skillId: string;
|
|
15
37
|
revisionId: string;
|
|
16
38
|
scopeVersion: number;
|
|
17
39
|
installationVersion?: number;
|
|
40
|
+
origin?: SkillReadOrigin;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
/** A selected built-in or session artifact: no ledger identity, and none is synthesized. */
|
|
44
|
+
export type SelectedSkillReadContent = Readonly<{
|
|
45
|
+
files: readonly SkillTextFile[];
|
|
46
|
+
origin: SkillReadOrigin;
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
type SkillReadIdentity = Partial<
|
|
50
|
+
Pick<SkillReadContent, "skillId" | "revisionId" | "scopeVersion" | "installationVersion">
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
export type SkillReadObservation = Readonly<{
|
|
54
|
+
caller: AttemptToolExecutionContext["caller"]["kind"];
|
|
55
|
+
kind: SkillReadKind;
|
|
56
|
+
/** Null when the read was refused before a Skill resolved. */
|
|
57
|
+
source: SkillUseSource | null;
|
|
58
|
+
/** The resolved Skill id, or the requested identifier when none resolved. */
|
|
59
|
+
skill: string;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Content-free read telemetry. It never changes what the caller receives: a
|
|
64
|
+
* model result gains only MCP `_meta`, which the model never sees and which
|
|
65
|
+
* only the tool-output event projection keeps.
|
|
66
|
+
*/
|
|
67
|
+
export type SkillReadTelemetry = Readonly<{
|
|
68
|
+
/** Ids in the Skill index the model sees this turn; null until known. */
|
|
69
|
+
indexedSkillIds: () => ReadonlySet<string> | null;
|
|
70
|
+
/** Whether skill_search returned this id earlier in this turn attempt. */
|
|
71
|
+
searched: (id: string) => boolean;
|
|
72
|
+
observe: (observation: SkillReadObservation) => void;
|
|
73
|
+
}>;
|
|
74
|
+
|
|
75
|
+
export type SkillReadActiveHistory = Readonly<{
|
|
76
|
+
/** Earlier skill_read results still in this session's active model history. */
|
|
77
|
+
readResults: () => Promise<ReadonlyArray<Record<string, unknown>>>;
|
|
78
|
+
/**
|
|
79
|
+
* This turn's model tool-output bound. Rows are bounded when stored, but a
|
|
80
|
+
* later model with a lower bound receives a shorter copy than the row holds.
|
|
81
|
+
*/
|
|
82
|
+
toolOutputTruncationTokens: () => number;
|
|
83
|
+
/** The lookup only saves tokens, so a failure returns the full text. */
|
|
84
|
+
onLookupFailed?: (error: unknown) => void;
|
|
18
85
|
}>;
|
|
19
86
|
|
|
87
|
+
const IDENTITY_KEYS = ["skillId", "revisionId", "scopeVersion", "installationVersion"] as const;
|
|
88
|
+
|
|
89
|
+
export const SKILL_UNAVAILABLE_MESSAGE = "Skill is not available in this session.";
|
|
90
|
+
/** Bounds on the Skill list an unresolved identifier returns. */
|
|
91
|
+
export const SKILL_UNAVAILABLE_LIST_MAX_ENTRIES = 25;
|
|
92
|
+
export const SKILL_UNAVAILABLE_LIST_MAX_BYTES = 4 * 1024;
|
|
93
|
+
// A shorter shared edge is chance: random hex ids share 1-3 leading characters often.
|
|
94
|
+
const MIN_RESEMBLANCE = 4;
|
|
95
|
+
|
|
96
|
+
export type AvailableSkill = Readonly<{ id: string; name: string }>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The error for an identifier that resolves to no Skill. It lists available
|
|
100
|
+
* Skills by id and name only, the descriptors the Skill index and skill_search
|
|
101
|
+
* already show the model, so the caller can retry instead of rediscovering
|
|
102
|
+
* the work from scratch. Entries resembling the requested identifier (shared
|
|
103
|
+
* prefix, suffix, or containment) come first, so a mistyped or spliced id
|
|
104
|
+
* keeps its likely targets inside the bound. The requested identifier is
|
|
105
|
+
* never echoed.
|
|
106
|
+
*/
|
|
107
|
+
export function unavailableSkillError(
|
|
108
|
+
requested: string,
|
|
109
|
+
available: readonly AvailableSkill[],
|
|
110
|
+
): Error {
|
|
111
|
+
const byId = new Map<string, AvailableSkill>();
|
|
112
|
+
for (const entry of available) if (!byId.has(entry.id)) byId.set(entry.id, entry);
|
|
113
|
+
const wanted = requested.toLowerCase();
|
|
114
|
+
const ranked = [...byId.values()]
|
|
115
|
+
.map((entry) => ({ entry, score: resemblance(wanted, entry) }))
|
|
116
|
+
.sort(
|
|
117
|
+
(a, b) =>
|
|
118
|
+
b.score - a.score || compare(a.entry.name, b.entry.name) || compare(a.entry.id, b.entry.id),
|
|
119
|
+
);
|
|
120
|
+
const header = [
|
|
121
|
+
SKILL_UNAVAILABLE_MESSAGE,
|
|
122
|
+
...(wanted.startsWith("repository:")
|
|
123
|
+
? ["Repository Skills are read with repository_skill_read."]
|
|
124
|
+
: []),
|
|
125
|
+
ranked.length === 0
|
|
126
|
+
? "No configured Skills are available in this session."
|
|
127
|
+
: "Retry with an exact id from these available Skills:",
|
|
128
|
+
].join("\n");
|
|
129
|
+
const lines = [header];
|
|
130
|
+
// Reserve room for the omission notice so the whole message stays bounded.
|
|
131
|
+
const budget = SKILL_UNAVAILABLE_LIST_MAX_BYTES - 128;
|
|
132
|
+
let bytes = Buffer.byteLength(header, "utf8");
|
|
133
|
+
let listed = 0;
|
|
134
|
+
for (const { entry } of ranked) {
|
|
135
|
+
const line = `- ${JSON.stringify({ id: entry.id, name: entry.name })}`;
|
|
136
|
+
const lineBytes = Buffer.byteLength(line, "utf8") + 1;
|
|
137
|
+
if (listed >= SKILL_UNAVAILABLE_LIST_MAX_ENTRIES || bytes + lineBytes > budget) continue;
|
|
138
|
+
lines.push(line);
|
|
139
|
+
bytes += lineBytes;
|
|
140
|
+
listed += 1;
|
|
141
|
+
}
|
|
142
|
+
const omitted = ranked.length - listed;
|
|
143
|
+
if (omitted > 0)
|
|
144
|
+
lines.push(`${omitted} more available Skills are not listed. Use skill_search to find them.`);
|
|
145
|
+
return new Error(lines.join("\n"));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resemblance(wanted: string, entry: AvailableSkill): number {
|
|
149
|
+
let best = 0;
|
|
150
|
+
for (const key of [entry.id.toLowerCase(), entry.name.toLowerCase()]) {
|
|
151
|
+
const contained =
|
|
152
|
+
wanted.includes(key) || key.includes(wanted) ? Math.min(wanted.length, key.length) : 0;
|
|
153
|
+
let prefix = 0;
|
|
154
|
+
while (prefix < wanted.length && prefix < key.length && wanted[prefix] === key[prefix])
|
|
155
|
+
prefix += 1;
|
|
156
|
+
let suffix = 0;
|
|
157
|
+
while (
|
|
158
|
+
suffix < wanted.length &&
|
|
159
|
+
suffix < key.length &&
|
|
160
|
+
wanted[wanted.length - 1 - suffix] === key[key.length - 1 - suffix]
|
|
161
|
+
)
|
|
162
|
+
suffix += 1;
|
|
163
|
+
best = Math.max(best, contained, prefix, suffix);
|
|
164
|
+
}
|
|
165
|
+
return best >= MIN_RESEMBLANCE ? best : 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function compare(left: string, right: string): number {
|
|
169
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
20
172
|
/** A first-party gateway definition, not a sandbox capability or second backend. */
|
|
21
173
|
export function createSkillReadAttemptToolDefinition(input: {
|
|
22
174
|
authorize: () => Promise<void>;
|
|
23
|
-
load: (
|
|
175
|
+
load: (
|
|
176
|
+
skill: string,
|
|
177
|
+
) => Promise<readonly SkillTextFile[] | SkillReadContent | SelectedSkillReadContent>;
|
|
178
|
+
/** Without it every read returns full text. */
|
|
179
|
+
activeHistory?: SkillReadActiveHistory;
|
|
180
|
+
telemetry?: SkillReadTelemetry;
|
|
24
181
|
}): AttemptToolDefinition {
|
|
25
182
|
return {
|
|
26
183
|
identity: { serverId: "opengeni", toolName: SKILL_READ_TOOL_NAME },
|
|
@@ -54,7 +211,7 @@ export function createSkillReadAttemptToolDefinition(input: {
|
|
|
54
211
|
},
|
|
55
212
|
source: "opengeni",
|
|
56
213
|
approval: "none",
|
|
57
|
-
execute: async (args) => {
|
|
214
|
+
execute: async (args, context) => {
|
|
58
215
|
if (
|
|
59
216
|
typeof args.skill !== "string" ||
|
|
60
217
|
!args.skill ||
|
|
@@ -68,31 +225,220 @@ export function createSkillReadAttemptToolDefinition(input: {
|
|
|
68
225
|
throw new Error("skill_read listFiles:true cannot be combined with paths.");
|
|
69
226
|
}
|
|
70
227
|
await input.authorize();
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
228
|
+
const skill = args.skill;
|
|
229
|
+
let origin: SkillReadOrigin | undefined;
|
|
230
|
+
let identity: SkillReadIdentity = {};
|
|
231
|
+
let read: { kind: Exclude<SkillReadKind, "refused">; result: AttemptToolResult };
|
|
232
|
+
try {
|
|
233
|
+
const loaded = await input.load(skill);
|
|
234
|
+
const content = "files" in loaded ? loaded : { files: loaded };
|
|
235
|
+
origin = "origin" in content ? content.origin : undefined;
|
|
236
|
+
if ("skillId" in content) {
|
|
237
|
+
identity = {
|
|
238
|
+
skillId: content.skillId,
|
|
239
|
+
revisionId: content.revisionId,
|
|
240
|
+
scopeVersion: content.scopeVersion,
|
|
241
|
+
...(content.installationVersion !== undefined
|
|
242
|
+
? { installationVersion: content.installationVersion }
|
|
243
|
+
: {}),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
read = await readResult(
|
|
247
|
+
content.files,
|
|
248
|
+
identity,
|
|
249
|
+
args as { skill: string; listFiles?: boolean; paths?: string[] },
|
|
250
|
+
context,
|
|
251
|
+
input.activeHistory,
|
|
252
|
+
);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
observe(input.telemetry, { caller: context.caller.kind, kind: "refused", origin, skill });
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
observe(input.telemetry, { caller: context.caller.kind, kind: read.kind, origin, skill });
|
|
258
|
+
return context.caller.kind === "model" && origin && input.telemetry
|
|
259
|
+
? withSkillUse(read, origin, identity, input.telemetry)
|
|
260
|
+
: read.result;
|
|
96
261
|
},
|
|
97
262
|
};
|
|
98
263
|
}
|
|
264
|
+
|
|
265
|
+
async function readResult(
|
|
266
|
+
files: readonly SkillTextFile[],
|
|
267
|
+
identity: SkillReadIdentity,
|
|
268
|
+
args: { skill: string; listFiles?: boolean; paths?: string[] },
|
|
269
|
+
context: AttemptToolExecutionContext,
|
|
270
|
+
activeHistory: SkillReadActiveHistory | undefined,
|
|
271
|
+
): Promise<{ kind: Exclude<SkillReadKind, "refused">; result: AttemptToolResult }> {
|
|
272
|
+
if (args.listFiles === true) {
|
|
273
|
+
return {
|
|
274
|
+
kind: "list",
|
|
275
|
+
result: textResult({ ...identity, ...listSkillPaths(files, PORTABLE_SKILL_MAX_FILES) }),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const selected = readSkillFiles(files, args.paths);
|
|
279
|
+
// Only the model's default SKILL.md read is deduplicated: explicit paths
|
|
280
|
+
// are the fresh-copy request, and a Codemode program never sees history.
|
|
281
|
+
if (
|
|
282
|
+
args.paths === undefined &&
|
|
283
|
+
context.caller.kind === "model" &&
|
|
284
|
+
activeHistory &&
|
|
285
|
+
(await inActiveHistory(activeHistory, identity, selected.files[0]!))
|
|
286
|
+
) {
|
|
287
|
+
return {
|
|
288
|
+
kind: "already_in_context",
|
|
289
|
+
result: textResult({
|
|
290
|
+
...identity,
|
|
291
|
+
alreadyInContext: true,
|
|
292
|
+
message: `Already in context: SKILL.md of ${JSON.stringify(args.skill)}${identity.revisionId ? ` revision ${identity.revisionId}` : ""} was returned earlier in this conversation and is unchanged. Use that copy. Re-read only if you need a fresh copy: call skill_read with paths ["SKILL.md"].`,
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (args.paths !== undefined) {
|
|
297
|
+
return { kind: "files", result: textResult({ ...identity, ...selected }) };
|
|
298
|
+
}
|
|
299
|
+
// The default read also indexes runnable files (path and first usage line)
|
|
300
|
+
// so the commands are visible without a sandbox checkout.
|
|
301
|
+
const scripts = skillScriptIndex(files);
|
|
302
|
+
return {
|
|
303
|
+
kind: "full",
|
|
304
|
+
result: textResult({ ...identity, ...selected, ...(scripts ?? {}) }),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Telemetry must never change or fail a read. */
|
|
309
|
+
function observe(
|
|
310
|
+
telemetry: SkillReadTelemetry | undefined,
|
|
311
|
+
read: {
|
|
312
|
+
caller: SkillReadObservation["caller"];
|
|
313
|
+
kind: SkillReadKind;
|
|
314
|
+
origin: SkillReadOrigin | undefined;
|
|
315
|
+
skill: string;
|
|
316
|
+
},
|
|
317
|
+
): void {
|
|
318
|
+
if (!telemetry) return;
|
|
319
|
+
try {
|
|
320
|
+
telemetry.observe({
|
|
321
|
+
caller: read.caller,
|
|
322
|
+
kind: read.kind,
|
|
323
|
+
source: read.origin?.source ?? null,
|
|
324
|
+
skill: read.origin?.id ?? read.skill,
|
|
325
|
+
});
|
|
326
|
+
} catch {
|
|
327
|
+
// Metrics are best effort.
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Adds the content-free Skill-use fact as MCP `_meta`. The model output is
|
|
333
|
+
* the text part alone, so it stays byte-identical. The fact is dropped
|
|
334
|
+
* rather than let it push a result past the model-visible size cap, which
|
|
335
|
+
* would change what the model receives.
|
|
336
|
+
*/
|
|
337
|
+
function withSkillUse(
|
|
338
|
+
read: { kind: Exclude<SkillReadKind, "refused">; result: AttemptToolResult },
|
|
339
|
+
origin: SkillReadOrigin,
|
|
340
|
+
identity: SkillReadIdentity,
|
|
341
|
+
telemetry: SkillReadTelemetry,
|
|
342
|
+
): AttemptToolResult {
|
|
343
|
+
try {
|
|
344
|
+
const text = read.result.content[0]?.type === "text" ? read.result.content[0].text : "";
|
|
345
|
+
// Parsing rejects anything outside the closed, content-free shape.
|
|
346
|
+
const use = SkillUse.parse({
|
|
347
|
+
id: origin.id,
|
|
348
|
+
source: origin.source,
|
|
349
|
+
...(origin.label ? { label: origin.label } : {}),
|
|
350
|
+
...(identity.revisionId
|
|
351
|
+
? { revisionId: identity.revisionId }
|
|
352
|
+
: origin.contentSha256
|
|
353
|
+
? { contentSha256: origin.contentSha256 }
|
|
354
|
+
: {}),
|
|
355
|
+
kind: read.kind,
|
|
356
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
357
|
+
inIndex: telemetry.indexedSkillIds()?.has(origin.id) ?? false,
|
|
358
|
+
searchedThisTurn: telemetry.searched(origin.id),
|
|
359
|
+
});
|
|
360
|
+
const annotated: AttemptToolResult = {
|
|
361
|
+
...read.result,
|
|
362
|
+
_meta: { ...read.result._meta, [SKILL_USE_META_KEY]: use as JsonValue },
|
|
363
|
+
};
|
|
364
|
+
return modelToolResultFits(annotated) ? annotated : read.result;
|
|
365
|
+
} catch {
|
|
366
|
+
return read.result;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function textResult(
|
|
371
|
+
output: NonNullable<AttemptToolResult["structuredContent"]>,
|
|
372
|
+
): AttemptToolResult {
|
|
373
|
+
return {
|
|
374
|
+
isError: false,
|
|
375
|
+
content: [{ type: "text", text: JSON.stringify(output) }],
|
|
376
|
+
structuredContent: output,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function inActiveHistory(
|
|
381
|
+
history: SkillReadActiveHistory,
|
|
382
|
+
identity: SkillReadIdentity,
|
|
383
|
+
file: SkillTextFile,
|
|
384
|
+
): Promise<boolean> {
|
|
385
|
+
let results: ReadonlyArray<Record<string, unknown>>;
|
|
386
|
+
try {
|
|
387
|
+
results = await history.readResults();
|
|
388
|
+
} catch (error) {
|
|
389
|
+
history.onLookupFailed?.(error);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
const tokens = history.toolOutputTruncationTokens();
|
|
393
|
+
// Judge the copy this turn's model receives, not the stored row.
|
|
394
|
+
return results.some((item) =>
|
|
395
|
+
returnedFile(boundModelToolOutputItem(item, tokens), identity, file),
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* True when an earlier result gave the model this exact file text under the
|
|
401
|
+
* same Skill identity. A truncated, spilled, or failed result does not parse
|
|
402
|
+
* as a Skill read, so it never counts.
|
|
403
|
+
*/
|
|
404
|
+
function returnedFile(
|
|
405
|
+
item: Record<string, unknown>,
|
|
406
|
+
identity: SkillReadIdentity,
|
|
407
|
+
file: SkillTextFile,
|
|
408
|
+
): boolean {
|
|
409
|
+
const text = resultText(item.output);
|
|
410
|
+
if (text === null) return false;
|
|
411
|
+
let previous: unknown;
|
|
412
|
+
try {
|
|
413
|
+
previous = JSON.parse(text);
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
if (!previous || typeof previous !== "object" || Array.isArray(previous)) return false;
|
|
418
|
+
const record = previous as Record<string, unknown>;
|
|
419
|
+
return (
|
|
420
|
+
IDENTITY_KEYS.every((key) => record[key] === identity[key]) &&
|
|
421
|
+
Array.isArray(record.files) &&
|
|
422
|
+
record.files.some(
|
|
423
|
+
(candidate: unknown) =>
|
|
424
|
+
!!candidate &&
|
|
425
|
+
typeof candidate === "object" &&
|
|
426
|
+
(candidate as SkillTextFile).path === file.path &&
|
|
427
|
+
(candidate as SkillTextFile).content === file.content,
|
|
428
|
+
)
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** The single text part of a function result, in any SDK output shape. */
|
|
433
|
+
function resultText(output: unknown): string | null {
|
|
434
|
+
if (typeof output === "string") return output;
|
|
435
|
+
const parts = Array.isArray(output) ? output : [output];
|
|
436
|
+
if (parts.length !== 1) return null;
|
|
437
|
+
const part = parts[0] as { type?: unknown; text?: unknown } | null;
|
|
438
|
+
return part &&
|
|
439
|
+
typeof part === "object" &&
|
|
440
|
+
(part.type === "text" || part.type === "input_text") &&
|
|
441
|
+
typeof part.text === "string"
|
|
442
|
+
? part.text
|
|
443
|
+
: null;
|
|
444
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AttemptToolDefinition } from "@opengeni/codemode";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { SkillWriteReceipt } from "@opengeni/contracts";
|
|
4
|
+
|
|
5
|
+
export const SkillRemoveRequest = z
|
|
6
|
+
.object({
|
|
7
|
+
operationId: z.uuid(),
|
|
8
|
+
skillId: z.uuid(),
|
|
9
|
+
expectedRevisionId: z.uuid().nullable(),
|
|
10
|
+
expectedScopeVersion: z.number().int().positive(),
|
|
11
|
+
reason: z.string().min(1).max(2000),
|
|
12
|
+
})
|
|
13
|
+
.strict();
|
|
14
|
+
|
|
15
|
+
export function createSkillRemoveAttemptToolDefinition(input: {
|
|
16
|
+
authorize: () => Promise<void>;
|
|
17
|
+
remove: (request: z.infer<typeof SkillRemoveRequest>) => Promise<SkillWriteReceipt>;
|
|
18
|
+
}): AttemptToolDefinition {
|
|
19
|
+
return {
|
|
20
|
+
identity: { serverId: "opengeni", toolName: "skill_remove" },
|
|
21
|
+
modelName: "skill_remove",
|
|
22
|
+
codemodePath: ["opengeni", "skill_remove"],
|
|
23
|
+
title: "Permanently remove Skill",
|
|
24
|
+
description:
|
|
25
|
+
"Permanently delete a saved Skill and ALL its stored revisions, not a recoverable hide. Conversations remain unchanged. Read the exact Skill first; supply its current active revision and scope version. Built-in, repository and inline session Skills cannot be removed. Automatic applies deletion; Review first retains an explicitly labeled deletion proposal for human review in Knowledge > Needs review; Off refuses changes. Reuse the same operationId and exact arguments after an uncertain result. Never report a pending proposal as deleted.",
|
|
26
|
+
inputSchema: JSON.parse(JSON.stringify(z.toJSONSchema(SkillRemoveRequest))),
|
|
27
|
+
annotations: {
|
|
28
|
+
readOnlyHint: false,
|
|
29
|
+
destructiveHint: true,
|
|
30
|
+
idempotentHint: true,
|
|
31
|
+
openWorldHint: false,
|
|
32
|
+
},
|
|
33
|
+
source: "opengeni",
|
|
34
|
+
approval: "none",
|
|
35
|
+
execute: async (args) => {
|
|
36
|
+
await input.authorize();
|
|
37
|
+
const output = await input.remove(SkillRemoveRequest.parse(args));
|
|
38
|
+
const text = JSON.stringify(output);
|
|
39
|
+
return {
|
|
40
|
+
isError: false,
|
|
41
|
+
content: [{ type: "text", text }],
|
|
42
|
+
structuredContent: JSON.parse(text),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|