@aopslabs/aops 0.2.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/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { logError, logSuccess } from '@aopslabs/xf-cli-ui';
|
|
4
|
+
import { applyCommonOptions, compactPayload, normalizeNonEmpty } from '../utils/command.js';
|
|
5
|
+
import { hydrateProjectContextWithApiState, invokeHostedToolWithApiState, requireApiState, unwrapHostedToolResult, } from '../utils/agent-gateway.js';
|
|
6
|
+
import { buildHostedSugarEnvelope, ensureGuardedWrite, ensureDestructiveWrite, } from '../utils/hosted-sugar.js';
|
|
7
|
+
import { preferProjectNameBinding, resolveOwnerScopeIdFromBinding, resolveOwnerScopeIdFromProjectRecord, resolveProjectBindingContext, } from '../utils/project-context.js';
|
|
8
|
+
const EXPERIENCE_TYPES = [
|
|
9
|
+
'technique',
|
|
10
|
+
'problem-solution',
|
|
11
|
+
'tool',
|
|
12
|
+
'script',
|
|
13
|
+
'pattern',
|
|
14
|
+
'anti-pattern',
|
|
15
|
+
'idea',
|
|
16
|
+
];
|
|
17
|
+
const EXPERIENCE_LIST_TOOL_ID = 'agentspace.experience-item.list-experience-items';
|
|
18
|
+
const EXPERIENCE_SEARCH_RANK_FETCH_LIMIT = 1000;
|
|
19
|
+
const EXPERIENCE_GET_TOOL_ID = 'agentspace.experience-item.get-by-id';
|
|
20
|
+
const EXPERIENCE_ADD_TOOL_ID = 'agentspace.experience-item.add-experience-item';
|
|
21
|
+
const EXPERIENCE_UPDATE_TOOL_ID = 'agentspace.experience-item.update-experience-item';
|
|
22
|
+
const EXPERIENCE_REMOVE_TOOL_ID = 'agentspace.experience-item.remove-experience-item';
|
|
23
|
+
function collectRepeatedOption(value, previous = []) {
|
|
24
|
+
const normalized = normalizeNonEmpty(value);
|
|
25
|
+
return normalized ? [...previous, normalized] : previous;
|
|
26
|
+
}
|
|
27
|
+
function toStringArray(values) {
|
|
28
|
+
return Array.isArray(values)
|
|
29
|
+
? values.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
|
|
30
|
+
: [];
|
|
31
|
+
}
|
|
32
|
+
function uniqueStrings(values) {
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const result = [];
|
|
35
|
+
for (const value of values) {
|
|
36
|
+
const normalized = normalizeNonEmpty(value);
|
|
37
|
+
if (!normalized)
|
|
38
|
+
continue;
|
|
39
|
+
const key = normalized.toLowerCase();
|
|
40
|
+
if (seen.has(key))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
result.push(normalized);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
function parseInteger(value) {
|
|
51
|
+
return Number.parseInt(value, 10);
|
|
52
|
+
}
|
|
53
|
+
function toNonNegativeInteger(value) {
|
|
54
|
+
if (value === undefined || value === null || value === '')
|
|
55
|
+
return undefined;
|
|
56
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value), 10);
|
|
57
|
+
if (!Number.isInteger(parsed) || parsed < 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
function normalizeExperienceType(value) {
|
|
62
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
63
|
+
if (EXPERIENCE_TYPES.includes(normalized))
|
|
64
|
+
return normalized;
|
|
65
|
+
throw new Error(`Unsupported experience type. Use one of: ${EXPERIENCE_TYPES.join(', ')}.`);
|
|
66
|
+
}
|
|
67
|
+
function expandAtFileContent(value) {
|
|
68
|
+
if (typeof value === 'string' && value.startsWith('@')) {
|
|
69
|
+
return readFileSync(value.slice(1).trim(), 'utf8');
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function buildResolvedContextRecord(context) {
|
|
74
|
+
return compactPayload({
|
|
75
|
+
repoRoot: context.repoRoot,
|
|
76
|
+
configPath: context.configPath,
|
|
77
|
+
configFound: context.configFound,
|
|
78
|
+
scopeId: context.scopeId,
|
|
79
|
+
projectId: context.projectId,
|
|
80
|
+
projectName: context.projectName,
|
|
81
|
+
projectSlug: context.projectSlug,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function resolveExperienceContext(options, params = {}) {
|
|
85
|
+
const resolved = await resolveProjectBindingContext(options, {
|
|
86
|
+
requireProject: params.requireScope === true,
|
|
87
|
+
});
|
|
88
|
+
const scopeId = normalizeNonEmpty(options.scopeId) ?? resolveOwnerScopeIdFromBinding(resolved);
|
|
89
|
+
if (params.requireScope === true &&
|
|
90
|
+
!scopeId &&
|
|
91
|
+
!normalizeNonEmpty(resolved.projectId) &&
|
|
92
|
+
!resolved.pendingServerProjectLookup) {
|
|
93
|
+
throw new Error('Experience context could not be resolved. Provide --project-id/--project-name/--project-slug. `--scope-id` remains a legacy/internal alias.');
|
|
94
|
+
}
|
|
95
|
+
return { ...resolved, scopeId };
|
|
96
|
+
}
|
|
97
|
+
function buildGatewayOptions(options, resolvedContext) {
|
|
98
|
+
return {
|
|
99
|
+
...options,
|
|
100
|
+
...preferProjectNameBinding(resolvedContext),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function unwrapResultData(result) {
|
|
104
|
+
if (isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data')) {
|
|
105
|
+
return result.data;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
async function hydrateProjectScopeContext(apiState, options, resolvedContext) {
|
|
110
|
+
const lookupResolved = await hydrateProjectContextWithApiState(apiState, options, resolvedContext);
|
|
111
|
+
resolvedContext = {
|
|
112
|
+
...resolvedContext,
|
|
113
|
+
...lookupResolved,
|
|
114
|
+
scopeId: normalizeNonEmpty(lookupResolved.scopeId) ?? resolvedContext.scopeId,
|
|
115
|
+
};
|
|
116
|
+
const projectId = normalizeNonEmpty(resolvedContext.projectId);
|
|
117
|
+
if (!projectId) {
|
|
118
|
+
return resolvedContext;
|
|
119
|
+
}
|
|
120
|
+
if (normalizeNonEmpty(resolvedContext.scopeId) === projectId && normalizeNonEmpty(resolvedContext.projectName)) {
|
|
121
|
+
return resolvedContext;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
125
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
126
|
+
toolId: 'agentspace.project.get-by-id',
|
|
127
|
+
input: { id: projectId },
|
|
128
|
+
});
|
|
129
|
+
const result = unwrapHostedToolResult(payload);
|
|
130
|
+
const project = unwrapResultData(result);
|
|
131
|
+
if (!isRecord(project)) {
|
|
132
|
+
return resolvedContext;
|
|
133
|
+
}
|
|
134
|
+
const scopeId = resolveOwnerScopeIdFromProjectRecord(project, resolvedContext.scopeId ?? projectId);
|
|
135
|
+
const projectName = normalizeNonEmpty(project.name) ?? resolvedContext.projectName;
|
|
136
|
+
return {
|
|
137
|
+
...resolvedContext,
|
|
138
|
+
scopeId: scopeId ?? resolvedContext.scopeId,
|
|
139
|
+
projectName,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return resolvedContext;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function parseSourceRefs(values) {
|
|
147
|
+
const refs = [];
|
|
148
|
+
for (const value of values ?? []) {
|
|
149
|
+
const normalized = normalizeNonEmpty(value);
|
|
150
|
+
if (!normalized)
|
|
151
|
+
continue;
|
|
152
|
+
try {
|
|
153
|
+
refs.push(JSON.parse(normalized));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
refs.push({ ref: normalized });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return refs;
|
|
160
|
+
}
|
|
161
|
+
function buildExperienceMeta(options) {
|
|
162
|
+
const sessionContext = compactPayload({
|
|
163
|
+
sessionId: normalizeNonEmpty(options.sessionId),
|
|
164
|
+
missionId: normalizeNonEmpty(options.missionId),
|
|
165
|
+
chatv3Seq: normalizeNonEmpty(options.chatv3Seq),
|
|
166
|
+
pmRefs: toStringArray(options.pmRef),
|
|
167
|
+
discussRefs: toStringArray(options.discussRef),
|
|
168
|
+
});
|
|
169
|
+
if (Object.keys(sessionContext).length === 0)
|
|
170
|
+
return undefined;
|
|
171
|
+
return {
|
|
172
|
+
experience: {
|
|
173
|
+
scope: 'session',
|
|
174
|
+
sessionContext,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function normalizeHostedExperienceRecord(row) {
|
|
179
|
+
const id = normalizeNonEmpty(row.id) ?? normalizeNonEmpty(row.localId);
|
|
180
|
+
const meta = isRecord(row.meta) ? row.meta : undefined;
|
|
181
|
+
return compactPayload({
|
|
182
|
+
id,
|
|
183
|
+
localId: id,
|
|
184
|
+
remoteId: normalizeNonEmpty(row.id) ?? normalizeNonEmpty(row.remoteId),
|
|
185
|
+
projectId: normalizeNonEmpty(row.projectId) ?? (meta ? normalizeNonEmpty(meta.projectId) : undefined),
|
|
186
|
+
scopeId: normalizeNonEmpty(row.scopeId),
|
|
187
|
+
type: normalizeNonEmpty(row.type),
|
|
188
|
+
title: normalizeNonEmpty(row.title),
|
|
189
|
+
problem: normalizeNonEmpty(row.problem),
|
|
190
|
+
solution: normalizeNonEmpty(row.solution),
|
|
191
|
+
areas: toStringArray(row.areas),
|
|
192
|
+
stack: toStringArray(row.stack),
|
|
193
|
+
commands: toStringArray(row.commands),
|
|
194
|
+
files: toStringArray(row.files),
|
|
195
|
+
sourceRefs: Array.isArray(row.sourceRefs) ? row.sourceRefs : undefined,
|
|
196
|
+
tags: toStringArray(row.tags),
|
|
197
|
+
confidence: normalizeNonEmpty(row.confidence),
|
|
198
|
+
reusability: normalizeNonEmpty(row.reusability),
|
|
199
|
+
createdAt: normalizeNonEmpty(row.createdAt),
|
|
200
|
+
updatedAt: normalizeNonEmpty(row.updatedAt),
|
|
201
|
+
storage: 'hosted',
|
|
202
|
+
content: normalizeNonEmpty(row.content),
|
|
203
|
+
meta,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function extractHostedRows(result) {
|
|
207
|
+
const data = unwrapResultData(result);
|
|
208
|
+
if (Array.isArray(data))
|
|
209
|
+
return data.filter(isRecord);
|
|
210
|
+
if (Array.isArray(result))
|
|
211
|
+
return result.filter(isRecord);
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
function buildExperienceListFilter(resolvedContext) {
|
|
215
|
+
return compactPayload({
|
|
216
|
+
scopeId: resolvedContext.scopeId,
|
|
217
|
+
scopeResolution: 'cascade',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function filterHostedExperienceRecords(records, options) {
|
|
221
|
+
const type = normalizeNonEmpty(options.type)?.toLowerCase();
|
|
222
|
+
const areas = toStringArray(options.area).map((entry) => entry.toLowerCase());
|
|
223
|
+
const stack = toStringArray(options.stack).map((entry) => entry.toLowerCase());
|
|
224
|
+
const tags = toStringArray(options.tag).map((entry) => entry.toLowerCase());
|
|
225
|
+
const limit = toNonNegativeInteger(options.limit);
|
|
226
|
+
const filtered = records.filter((record) => {
|
|
227
|
+
if (type && normalizeNonEmpty(record.type)?.toLowerCase() !== type)
|
|
228
|
+
return false;
|
|
229
|
+
const recordAreas = toStringArray(record.areas).map((entry) => entry.toLowerCase());
|
|
230
|
+
const recordStack = toStringArray(record.stack).map((entry) => entry.toLowerCase());
|
|
231
|
+
const recordTags = toStringArray(record.tags).map((entry) => entry.toLowerCase());
|
|
232
|
+
if (areas.length > 0 && !areas.every((area) => recordAreas.includes(area)))
|
|
233
|
+
return false;
|
|
234
|
+
if (stack.length > 0 && !stack.every((entry) => recordStack.includes(entry)))
|
|
235
|
+
return false;
|
|
236
|
+
if (tags.length > 0 && !tags.every((tag) => recordTags.includes(tag)))
|
|
237
|
+
return false;
|
|
238
|
+
return true;
|
|
239
|
+
});
|
|
240
|
+
return typeof limit === 'number' && limit > 0 ? filtered.slice(0, limit) : filtered;
|
|
241
|
+
}
|
|
242
|
+
function scoreHostedExperienceRecord(record, query) {
|
|
243
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
244
|
+
if (terms.length === 0)
|
|
245
|
+
return 0;
|
|
246
|
+
const title = (normalizeNonEmpty(record.title) ?? '').toLowerCase();
|
|
247
|
+
const classification = [
|
|
248
|
+
...toStringArray(record.tags),
|
|
249
|
+
...toStringArray(record.areas),
|
|
250
|
+
...toStringArray(record.stack),
|
|
251
|
+
normalizeNonEmpty(record.type),
|
|
252
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
253
|
+
const structured = [
|
|
254
|
+
normalizeNonEmpty(record.problem),
|
|
255
|
+
normalizeNonEmpty(record.solution),
|
|
256
|
+
...toStringArray(record.commands),
|
|
257
|
+
...toStringArray(record.files),
|
|
258
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
259
|
+
const content = (normalizeNonEmpty(record.content) ?? '').toLowerCase();
|
|
260
|
+
let score = 0;
|
|
261
|
+
for (const term of terms) {
|
|
262
|
+
if (title.includes(term))
|
|
263
|
+
score += 8;
|
|
264
|
+
if (classification.includes(term))
|
|
265
|
+
score += 5;
|
|
266
|
+
if (structured.includes(term))
|
|
267
|
+
score += 4;
|
|
268
|
+
if (content.includes(term))
|
|
269
|
+
score += 2;
|
|
270
|
+
}
|
|
271
|
+
return score;
|
|
272
|
+
}
|
|
273
|
+
function emitExperienceCommandError(params) {
|
|
274
|
+
const message = params.error instanceof Error ? params.error.message : String(params.error);
|
|
275
|
+
if (params.options.json) {
|
|
276
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
277
|
+
command: params.command,
|
|
278
|
+
toolId: params.toolId,
|
|
279
|
+
resolvedContext: params.resolvedContext ? buildResolvedContextRecord(params.resolvedContext) : {},
|
|
280
|
+
input: params.input ?? {},
|
|
281
|
+
result: { error: { message } },
|
|
282
|
+
}), null, 2));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
logError(message);
|
|
286
|
+
}
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
}
|
|
289
|
+
function buildCaptureDataInput(options, context) {
|
|
290
|
+
const title = normalizeNonEmpty(options.title);
|
|
291
|
+
if (!title)
|
|
292
|
+
throw new Error('Provide --title.');
|
|
293
|
+
const content = normalizeNonEmpty(expandAtFileContent(options.content))
|
|
294
|
+
?? normalizeNonEmpty(options.solution)
|
|
295
|
+
?? normalizeNonEmpty(options.problem);
|
|
296
|
+
if (!content)
|
|
297
|
+
throw new Error('Provide --content, --solution, or --problem.');
|
|
298
|
+
return compactPayload({
|
|
299
|
+
scopeId: context.scopeId,
|
|
300
|
+
type: normalizeExperienceType(options.type ?? 'technique'),
|
|
301
|
+
title,
|
|
302
|
+
problem: normalizeNonEmpty(options.problem),
|
|
303
|
+
solution: normalizeNonEmpty(options.solution),
|
|
304
|
+
content,
|
|
305
|
+
areas: toStringArray(options.area),
|
|
306
|
+
stack: toStringArray(options.stack),
|
|
307
|
+
commands: toStringArray(options.command),
|
|
308
|
+
files: toStringArray(options.file),
|
|
309
|
+
sourceRefs: parseSourceRefs(options.sourceRef),
|
|
310
|
+
tags: uniqueStrings(toStringArray(options.tag)),
|
|
311
|
+
confidence: normalizeNonEmpty(options.confidence),
|
|
312
|
+
reusability: normalizeNonEmpty(options.reusability),
|
|
313
|
+
meta: buildExperienceMeta(options),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
export async function runExperienceCapture(options = {}) {
|
|
317
|
+
let resolvedContext;
|
|
318
|
+
let apiState;
|
|
319
|
+
try {
|
|
320
|
+
ensureGuardedWrite(options, 'This command writes an Agentspace experience item.');
|
|
321
|
+
apiState = await requireApiState(options);
|
|
322
|
+
if (!apiState)
|
|
323
|
+
return;
|
|
324
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: true });
|
|
325
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
329
|
+
process.exitCode = 1;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const data = buildCaptureDataInput(options, resolvedContext);
|
|
334
|
+
const input = { data };
|
|
335
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
336
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
337
|
+
tenantId: options.tenantId,
|
|
338
|
+
locale: options.locale,
|
|
339
|
+
fallbackLocale: options.fallbackLocale,
|
|
340
|
+
timeoutMs: options.timeoutMs,
|
|
341
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
342
|
+
accessToken: options.accessToken,
|
|
343
|
+
refreshToken: options.refreshToken,
|
|
344
|
+
toolId: EXPERIENCE_ADD_TOOL_ID,
|
|
345
|
+
input,
|
|
346
|
+
preview: options.preview,
|
|
347
|
+
apply: options.apply,
|
|
348
|
+
idempotencyKey: options.idempotencyKey,
|
|
349
|
+
});
|
|
350
|
+
const result = unwrapHostedToolResult(payload);
|
|
351
|
+
const row = unwrapResultData(result);
|
|
352
|
+
const record = isRecord(row) ? normalizeHostedExperienceRecord(row) : result;
|
|
353
|
+
if (options.json) {
|
|
354
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
355
|
+
command: 'experience.capture',
|
|
356
|
+
toolId: EXPERIENCE_ADD_TOOL_ID,
|
|
357
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
358
|
+
input,
|
|
359
|
+
result: record,
|
|
360
|
+
}), null, 2));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
logSuccess('Experience item captured.');
|
|
364
|
+
console.log(JSON.stringify(record, null, 2));
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
emitExperienceCommandError({
|
|
368
|
+
options,
|
|
369
|
+
command: 'experience.capture',
|
|
370
|
+
toolId: EXPERIENCE_ADD_TOOL_ID,
|
|
371
|
+
resolvedContext,
|
|
372
|
+
error,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
export async function runExperienceList(options = {}) {
|
|
377
|
+
let resolvedContext;
|
|
378
|
+
let apiState;
|
|
379
|
+
try {
|
|
380
|
+
apiState = await requireApiState(options);
|
|
381
|
+
if (!apiState)
|
|
382
|
+
return;
|
|
383
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: true });
|
|
384
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
388
|
+
process.exitCode = 1;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const input = compactPayload({
|
|
393
|
+
filter: buildExperienceListFilter(resolvedContext),
|
|
394
|
+
options: compactPayload({ limit: toNonNegativeInteger(options.limit) }),
|
|
395
|
+
});
|
|
396
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
397
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
398
|
+
tenantId: options.tenantId,
|
|
399
|
+
locale: options.locale,
|
|
400
|
+
fallbackLocale: options.fallbackLocale,
|
|
401
|
+
timeoutMs: options.timeoutMs,
|
|
402
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
403
|
+
accessToken: options.accessToken,
|
|
404
|
+
refreshToken: options.refreshToken,
|
|
405
|
+
toolId: EXPERIENCE_LIST_TOOL_ID,
|
|
406
|
+
input,
|
|
407
|
+
});
|
|
408
|
+
const result = unwrapHostedToolResult(payload);
|
|
409
|
+
const records = extractHostedRows(result).map(normalizeHostedExperienceRecord);
|
|
410
|
+
const filtered = filterHostedExperienceRecords(records, options);
|
|
411
|
+
const data = { data: filtered };
|
|
412
|
+
if (options.json) {
|
|
413
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
414
|
+
command: 'experience.list',
|
|
415
|
+
toolId: EXPERIENCE_LIST_TOOL_ID,
|
|
416
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
417
|
+
input,
|
|
418
|
+
result: data,
|
|
419
|
+
}), null, 2));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
logSuccess('Experience items loaded.');
|
|
423
|
+
console.log(JSON.stringify(data, null, 2));
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
emitExperienceCommandError({
|
|
427
|
+
options,
|
|
428
|
+
command: 'experience.list',
|
|
429
|
+
toolId: EXPERIENCE_LIST_TOOL_ID,
|
|
430
|
+
resolvedContext,
|
|
431
|
+
error,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
export async function runExperienceGet(options = {}) {
|
|
436
|
+
let resolvedContext;
|
|
437
|
+
let apiState;
|
|
438
|
+
try {
|
|
439
|
+
const id = normalizeNonEmpty(options.id);
|
|
440
|
+
if (!id)
|
|
441
|
+
throw new Error('Provide --id.');
|
|
442
|
+
apiState = await requireApiState(options);
|
|
443
|
+
if (!apiState)
|
|
444
|
+
return;
|
|
445
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: false });
|
|
446
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
447
|
+
const input = { id };
|
|
448
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
449
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
450
|
+
tenantId: options.tenantId,
|
|
451
|
+
locale: options.locale,
|
|
452
|
+
fallbackLocale: options.fallbackLocale,
|
|
453
|
+
timeoutMs: options.timeoutMs,
|
|
454
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
455
|
+
accessToken: options.accessToken,
|
|
456
|
+
refreshToken: options.refreshToken,
|
|
457
|
+
toolId: EXPERIENCE_GET_TOOL_ID,
|
|
458
|
+
input,
|
|
459
|
+
});
|
|
460
|
+
const result = unwrapHostedToolResult(payload);
|
|
461
|
+
const row = unwrapResultData(result);
|
|
462
|
+
if (!isRecord(row))
|
|
463
|
+
throw new Error('Experience item was not found.');
|
|
464
|
+
const data = { data: normalizeHostedExperienceRecord(row) };
|
|
465
|
+
if (options.json) {
|
|
466
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
467
|
+
command: 'experience.get',
|
|
468
|
+
toolId: EXPERIENCE_GET_TOOL_ID,
|
|
469
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
470
|
+
input,
|
|
471
|
+
result: data,
|
|
472
|
+
}), null, 2));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
logSuccess('Experience item loaded.');
|
|
476
|
+
console.log(JSON.stringify(data, null, 2));
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
emitExperienceCommandError({
|
|
480
|
+
options,
|
|
481
|
+
command: 'experience.get',
|
|
482
|
+
toolId: EXPERIENCE_GET_TOOL_ID,
|
|
483
|
+
resolvedContext,
|
|
484
|
+
error,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
export async function runExperienceSearch(options = {}) {
|
|
489
|
+
let resolvedContext;
|
|
490
|
+
let apiState;
|
|
491
|
+
try {
|
|
492
|
+
const query = normalizeNonEmpty(options.query);
|
|
493
|
+
if (!query)
|
|
494
|
+
throw new Error('Provide --query.');
|
|
495
|
+
apiState = await requireApiState(options);
|
|
496
|
+
if (!apiState)
|
|
497
|
+
return;
|
|
498
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: true });
|
|
499
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
503
|
+
process.exitCode = 1;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
const query = normalizeNonEmpty(options.query);
|
|
508
|
+
const input = compactPayload({
|
|
509
|
+
filter: buildExperienceListFilter(resolvedContext),
|
|
510
|
+
options: compactPayload({ limit: EXPERIENCE_SEARCH_RANK_FETCH_LIMIT }),
|
|
511
|
+
});
|
|
512
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
513
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
514
|
+
tenantId: options.tenantId,
|
|
515
|
+
locale: options.locale,
|
|
516
|
+
fallbackLocale: options.fallbackLocale,
|
|
517
|
+
timeoutMs: options.timeoutMs,
|
|
518
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
519
|
+
accessToken: options.accessToken,
|
|
520
|
+
refreshToken: options.refreshToken,
|
|
521
|
+
toolId: EXPERIENCE_LIST_TOOL_ID,
|
|
522
|
+
input,
|
|
523
|
+
});
|
|
524
|
+
const result = unwrapHostedToolResult(payload);
|
|
525
|
+
const rawRows = extractHostedRows(result);
|
|
526
|
+
if (rawRows.length >= EXPERIENCE_SEARCH_RANK_FETCH_LIMIT) {
|
|
527
|
+
console.error(`[experience search] ranked over the first ${EXPERIENCE_SEARCH_RANK_FETCH_LIMIT} server rows; narrow --query/--type/--area/--stack/--tag if an expected match is missing.`);
|
|
528
|
+
}
|
|
529
|
+
const records = filterHostedExperienceRecords(rawRows.map(normalizeHostedExperienceRecord), { ...options, limit: undefined });
|
|
530
|
+
const limit = toNonNegativeInteger(options.limit) ?? 10;
|
|
531
|
+
const ranked = records
|
|
532
|
+
.map((record) => ({ record, score: scoreHostedExperienceRecord(record, query) }))
|
|
533
|
+
.filter((entry) => entry.score > 0)
|
|
534
|
+
.sort((left, right) => right.score - left.score
|
|
535
|
+
|| (normalizeNonEmpty(right.record.updatedAt) ?? '').localeCompare(normalizeNonEmpty(left.record.updatedAt) ?? ''))
|
|
536
|
+
.slice(0, limit > 0 ? limit : 10);
|
|
537
|
+
const data = { data: ranked.map(({ record, score }) => ({ ...record, score })) };
|
|
538
|
+
if (options.json) {
|
|
539
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
540
|
+
command: 'experience.search',
|
|
541
|
+
toolId: EXPERIENCE_LIST_TOOL_ID,
|
|
542
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
543
|
+
input: compactPayload({ ...input, query }),
|
|
544
|
+
result: data,
|
|
545
|
+
}), null, 2));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
logSuccess('Experience search completed.');
|
|
549
|
+
console.log(JSON.stringify(data, null, 2));
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
emitExperienceCommandError({
|
|
553
|
+
options,
|
|
554
|
+
command: 'experience.search',
|
|
555
|
+
toolId: EXPERIENCE_LIST_TOOL_ID,
|
|
556
|
+
resolvedContext,
|
|
557
|
+
error,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function buildUpdatePatch(options) {
|
|
562
|
+
return compactPayload({
|
|
563
|
+
type: options.type ? normalizeExperienceType(options.type) : undefined,
|
|
564
|
+
title: normalizeNonEmpty(options.title),
|
|
565
|
+
problem: normalizeNonEmpty(options.problem),
|
|
566
|
+
solution: normalizeNonEmpty(options.solution),
|
|
567
|
+
content: normalizeNonEmpty(expandAtFileContent(options.content)),
|
|
568
|
+
areas: toStringArray(options.area).length > 0 ? uniqueStrings(toStringArray(options.area)) : undefined,
|
|
569
|
+
stack: toStringArray(options.stack).length > 0 ? uniqueStrings(toStringArray(options.stack)) : undefined,
|
|
570
|
+
commands: toStringArray(options.command).length > 0 ? uniqueStrings(toStringArray(options.command)) : undefined,
|
|
571
|
+
files: toStringArray(options.file).length > 0 ? uniqueStrings(toStringArray(options.file)) : undefined,
|
|
572
|
+
sourceRefs: parseSourceRefs(options.sourceRef).length > 0 ? parseSourceRefs(options.sourceRef) : undefined,
|
|
573
|
+
tags: toStringArray(options.tag).length > 0 ? uniqueStrings(toStringArray(options.tag)) : undefined,
|
|
574
|
+
confidence: normalizeNonEmpty(options.confidence),
|
|
575
|
+
reusability: normalizeNonEmpty(options.reusability),
|
|
576
|
+
meta: buildExperienceMeta(options),
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
export async function runExperienceUpdate(options = {}) {
|
|
580
|
+
let resolvedContext;
|
|
581
|
+
let apiState;
|
|
582
|
+
try {
|
|
583
|
+
const id = normalizeNonEmpty(options.id);
|
|
584
|
+
if (!id)
|
|
585
|
+
throw new Error('Provide --id.');
|
|
586
|
+
ensureGuardedWrite(options, 'This command updates an Agentspace experience item.');
|
|
587
|
+
apiState = await requireApiState(options);
|
|
588
|
+
if (!apiState)
|
|
589
|
+
return;
|
|
590
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: false });
|
|
591
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
595
|
+
process.exitCode = 1;
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
const id = normalizeNonEmpty(options.id);
|
|
600
|
+
const patch = buildUpdatePatch(options);
|
|
601
|
+
if (Object.keys(patch).length === 0) {
|
|
602
|
+
throw new Error('Provide at least one patch field such as --title, --content, --area, --stack, --command, --file, or --tag.');
|
|
603
|
+
}
|
|
604
|
+
const input = { id, patch };
|
|
605
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
606
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
607
|
+
tenantId: options.tenantId,
|
|
608
|
+
locale: options.locale,
|
|
609
|
+
fallbackLocale: options.fallbackLocale,
|
|
610
|
+
timeoutMs: options.timeoutMs,
|
|
611
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
612
|
+
accessToken: options.accessToken,
|
|
613
|
+
refreshToken: options.refreshToken,
|
|
614
|
+
toolId: EXPERIENCE_UPDATE_TOOL_ID,
|
|
615
|
+
input,
|
|
616
|
+
preview: options.preview,
|
|
617
|
+
apply: options.apply,
|
|
618
|
+
idempotencyKey: options.idempotencyKey,
|
|
619
|
+
});
|
|
620
|
+
const result = unwrapHostedToolResult(payload);
|
|
621
|
+
const row = unwrapResultData(result);
|
|
622
|
+
const data = { data: isRecord(row) ? normalizeHostedExperienceRecord(row) : result };
|
|
623
|
+
if (options.json) {
|
|
624
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
625
|
+
command: 'experience.update',
|
|
626
|
+
toolId: EXPERIENCE_UPDATE_TOOL_ID,
|
|
627
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
628
|
+
input,
|
|
629
|
+
result: data,
|
|
630
|
+
}), null, 2));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
logSuccess('Experience item updated.');
|
|
634
|
+
console.log(JSON.stringify(data, null, 2));
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
emitExperienceCommandError({
|
|
638
|
+
options,
|
|
639
|
+
command: 'experience.update',
|
|
640
|
+
toolId: EXPERIENCE_UPDATE_TOOL_ID,
|
|
641
|
+
resolvedContext,
|
|
642
|
+
error,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
export async function runExperienceDelete(options = {}) {
|
|
647
|
+
let resolvedContext;
|
|
648
|
+
let apiState;
|
|
649
|
+
try {
|
|
650
|
+
const id = normalizeNonEmpty(options.id);
|
|
651
|
+
if (!id)
|
|
652
|
+
throw new Error('Provide --id.');
|
|
653
|
+
ensureDestructiveWrite(options, 'This command deletes an Agentspace experience item.');
|
|
654
|
+
apiState = await requireApiState(options);
|
|
655
|
+
if (!apiState)
|
|
656
|
+
return;
|
|
657
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: false });
|
|
658
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
662
|
+
process.exitCode = 1;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const id = normalizeNonEmpty(options.id);
|
|
667
|
+
const input = { id };
|
|
668
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
669
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
670
|
+
tenantId: options.tenantId,
|
|
671
|
+
locale: options.locale,
|
|
672
|
+
fallbackLocale: options.fallbackLocale,
|
|
673
|
+
timeoutMs: options.timeoutMs,
|
|
674
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
675
|
+
accessToken: options.accessToken,
|
|
676
|
+
refreshToken: options.refreshToken,
|
|
677
|
+
toolId: EXPERIENCE_REMOVE_TOOL_ID,
|
|
678
|
+
input,
|
|
679
|
+
preview: options.preview,
|
|
680
|
+
apply: options.apply,
|
|
681
|
+
confirm: options.confirm,
|
|
682
|
+
idempotencyKey: options.idempotencyKey,
|
|
683
|
+
});
|
|
684
|
+
const result = unwrapHostedToolResult(payload);
|
|
685
|
+
const row = unwrapResultData(result);
|
|
686
|
+
const data = isRecord(row) ? { deleted: true, id, ...row } : { deleted: true, id };
|
|
687
|
+
if (options.json) {
|
|
688
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
689
|
+
command: 'experience.delete',
|
|
690
|
+
toolId: EXPERIENCE_REMOVE_TOOL_ID,
|
|
691
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
692
|
+
input,
|
|
693
|
+
result: data,
|
|
694
|
+
}), null, 2));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
logSuccess('Experience item deleted.');
|
|
698
|
+
console.log(JSON.stringify(data, null, 2));
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
emitExperienceCommandError({
|
|
702
|
+
options,
|
|
703
|
+
command: 'experience.delete',
|
|
704
|
+
toolId: EXPERIENCE_REMOVE_TOOL_ID,
|
|
705
|
+
resolvedContext,
|
|
706
|
+
error,
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const EXPERIENCE_PROMOTE_TOOL_ID = 'agentspace.memory-item.promote-from-experience';
|
|
711
|
+
function buildPromoteOverrides(options) {
|
|
712
|
+
return compactPayload({
|
|
713
|
+
kind: normalizeNonEmpty(options.kind),
|
|
714
|
+
durability: normalizeNonEmpty(options.durability),
|
|
715
|
+
content: normalizeNonEmpty(expandAtFileContent(options.content)),
|
|
716
|
+
tags: uniqueStrings(toStringArray(options.tag)),
|
|
717
|
+
playbookId: normalizeNonEmpty(options.playbookId),
|
|
718
|
+
playbookScope: normalizeNonEmpty(options.playbookScope),
|
|
719
|
+
playbookArea: normalizeNonEmpty(options.playbookArea),
|
|
720
|
+
appliesWhen: normalizeNonEmpty(options.appliesWhen),
|
|
721
|
+
steps: uniqueStrings(toStringArray(options.step)),
|
|
722
|
+
enforcement: normalizeNonEmpty(options.enforcement),
|
|
723
|
+
reviewState: normalizeNonEmpty(options.reviewState),
|
|
724
|
+
supersedes: normalizeNonEmpty(options.supersedes),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
export async function runExperiencePromote(options = {}) {
|
|
728
|
+
const asPlaybook = options.asPlaybook === true;
|
|
729
|
+
const command = asPlaybook ? 'experience.promote.playbook' : 'experience.promote';
|
|
730
|
+
let resolvedContext;
|
|
731
|
+
let apiState;
|
|
732
|
+
try {
|
|
733
|
+
const id = normalizeNonEmpty(options.id);
|
|
734
|
+
if (!id)
|
|
735
|
+
throw new Error('Provide --id.');
|
|
736
|
+
ensureGuardedWrite(options, 'This command promotes an Agentspace experience item into a memory item.');
|
|
737
|
+
apiState = await requireApiState(options);
|
|
738
|
+
if (!apiState)
|
|
739
|
+
return;
|
|
740
|
+
resolvedContext = await resolveExperienceContext(options, { requireScope: false });
|
|
741
|
+
resolvedContext = await hydrateProjectScopeContext(apiState, options, resolvedContext);
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
745
|
+
process.exitCode = 1;
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
const experienceId = normalizeNonEmpty(options.id);
|
|
750
|
+
const overrides = buildPromoteOverrides(options);
|
|
751
|
+
const input = compactPayload({
|
|
752
|
+
experienceId,
|
|
753
|
+
asPlaybook: asPlaybook ? true : undefined,
|
|
754
|
+
overrides: Object.keys(overrides).length > 0 ? overrides : undefined,
|
|
755
|
+
});
|
|
756
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
757
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
758
|
+
tenantId: options.tenantId,
|
|
759
|
+
locale: options.locale,
|
|
760
|
+
fallbackLocale: options.fallbackLocale,
|
|
761
|
+
timeoutMs: options.timeoutMs,
|
|
762
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
763
|
+
accessToken: options.accessToken,
|
|
764
|
+
refreshToken: options.refreshToken,
|
|
765
|
+
toolId: EXPERIENCE_PROMOTE_TOOL_ID,
|
|
766
|
+
input,
|
|
767
|
+
preview: options.preview,
|
|
768
|
+
apply: options.apply,
|
|
769
|
+
idempotencyKey: options.idempotencyKey,
|
|
770
|
+
});
|
|
771
|
+
const result = unwrapHostedToolResult(payload);
|
|
772
|
+
const row = unwrapResultData(result);
|
|
773
|
+
const data = { data: isRecord(row) ? row : result };
|
|
774
|
+
if (options.json) {
|
|
775
|
+
console.log(JSON.stringify(buildHostedSugarEnvelope({
|
|
776
|
+
command,
|
|
777
|
+
toolId: EXPERIENCE_PROMOTE_TOOL_ID,
|
|
778
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
779
|
+
input,
|
|
780
|
+
result: data,
|
|
781
|
+
}), null, 2));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
logSuccess(asPlaybook ? 'Experience promoted into a playbook memory item.' : 'Experience promoted into a durable memory item.');
|
|
785
|
+
console.log(JSON.stringify(data, null, 2));
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
emitExperienceCommandError({
|
|
789
|
+
options,
|
|
790
|
+
command,
|
|
791
|
+
toolId: EXPERIENCE_PROMOTE_TOOL_ID,
|
|
792
|
+
resolvedContext,
|
|
793
|
+
input: compactPayload({ id: normalizeNonEmpty(options.id), asPlaybook: asPlaybook ? true : undefined }),
|
|
794
|
+
error,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
export function makeExperienceCommand() {
|
|
799
|
+
const cmd = new Command('exp')
|
|
800
|
+
.alias('experience')
|
|
801
|
+
.description('Author and read hosted Agentspace experience items (server-first)');
|
|
802
|
+
applyCommonOptions(cmd, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
803
|
+
cmd.addHelpText('after', `
|
|
804
|
+
Experience is server-first: every subcommand reads and writes the hosted
|
|
805
|
+
\`agentspace.experience-item.*\` ops through the agent gateway. The hosted store
|
|
806
|
+
is the single source of truth.
|
|
807
|
+
|
|
808
|
+
Use experience for techniques, scripts, tools, problem/solution notes, and reusable agent learnings.
|
|
809
|
+
Use mem for project state, durable decisions, handoff/resume, sticky bootstrap rules.
|
|
810
|
+
|
|
811
|
+
Capture and read hosted experience:
|
|
812
|
+
aops-cli exp capture --type problem-solution --title "<title>" --content "<notes>" --apply --json
|
|
813
|
+
aops-cli exp list --type problem-solution --json
|
|
814
|
+
aops-cli exp get --id <id> --json
|
|
815
|
+
aops-cli exp search --query "<terms>" --json
|
|
816
|
+
|
|
817
|
+
Promote a hosted experience into a durable memory item, or a playbook:
|
|
818
|
+
aops-cli exp promote --id <id> --apply --json
|
|
819
|
+
aops-cli exp promote --id <id> --as-playbook --playbook-area backend --review-state accepted --apply --json
|
|
820
|
+
`);
|
|
821
|
+
const capture = cmd.command('capture')
|
|
822
|
+
.description('Capture a hosted experience item')
|
|
823
|
+
.option('--type <type>', `Experience type: ${EXPERIENCE_TYPES.join('|')} (default: technique)`)
|
|
824
|
+
.option('--title <title>', 'Experience title')
|
|
825
|
+
.option('--content <text>', 'Narrative notes (supports @file)')
|
|
826
|
+
.option('--problem <text>', 'Problem statement')
|
|
827
|
+
.option('--solution <text>', 'Solution or technique')
|
|
828
|
+
.option('--area <area>', 'Area tag; repeatable', collectRepeatedOption, [])
|
|
829
|
+
.option('--stack <stack>', 'Stack/tooling tag; repeatable', collectRepeatedOption, [])
|
|
830
|
+
.option('--command <command>', 'Useful command/script; repeatable', collectRepeatedOption, [])
|
|
831
|
+
.option('--file <path>', 'Relevant file path; repeatable', collectRepeatedOption, [])
|
|
832
|
+
.option('--tag <tag>', 'General tag; repeatable', collectRepeatedOption, [])
|
|
833
|
+
.option('--source-ref <json-or-ref>', 'Source reference JSON or string; repeatable', collectRepeatedOption, [])
|
|
834
|
+
.option('--session-id <id>', 'Session id for session-scoped experience context')
|
|
835
|
+
.option('--mission-id <id>', 'Mission id for session-scoped experience context')
|
|
836
|
+
.option('--chatv3-seq <seq>', 'ChatV3 sequence anchor for session-scoped experience context')
|
|
837
|
+
.option('--pm-ref <ref>', 'Projectman reference for session-scoped experience context; repeatable', collectRepeatedOption, [])
|
|
838
|
+
.option('--discuss-ref <ref>', 'Discuss topic/turn reference for session-scoped experience context; repeatable', collectRepeatedOption, [])
|
|
839
|
+
.option('--confidence <level>', 'Confidence note')
|
|
840
|
+
.option('--reusability <level>', 'Reusability note')
|
|
841
|
+
.option('--preview', 'Preview the hosted write without applying')
|
|
842
|
+
.option('--apply', 'Write the item')
|
|
843
|
+
.option('--idempotency-key <key>', 'Idempotency key for the hosted write')
|
|
844
|
+
.action((options) => runExperienceCapture({ ...cmd.opts(), ...options }));
|
|
845
|
+
applyCommonOptions(capture, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
846
|
+
const list = cmd.command('list')
|
|
847
|
+
.description('List hosted experience items within the current owner scope')
|
|
848
|
+
.option('--type <type>', 'Filter by type')
|
|
849
|
+
.option('--area <area>', 'Filter by area; repeatable', collectRepeatedOption, [])
|
|
850
|
+
.option('--stack <stack>', 'Filter by stack; repeatable', collectRepeatedOption, [])
|
|
851
|
+
.option('--tag <tag>', 'Filter by tag; repeatable', collectRepeatedOption, [])
|
|
852
|
+
.option('--limit <n>', 'Limit result count', parseInteger)
|
|
853
|
+
.action((options) => runExperienceList({ ...cmd.opts(), ...options }));
|
|
854
|
+
applyCommonOptions(list, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
855
|
+
const get = cmd.command('get')
|
|
856
|
+
.description('Get one hosted experience item by id')
|
|
857
|
+
.requiredOption('--id <id>', 'Experience item id')
|
|
858
|
+
.action((options) => runExperienceGet({ ...cmd.opts(), ...options }));
|
|
859
|
+
applyCommonOptions(get, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
860
|
+
const search = cmd.command('search')
|
|
861
|
+
.description('Lexically rank hosted experience items (server-truth rows ranked client-side)')
|
|
862
|
+
.requiredOption('--query <text>', 'Search query')
|
|
863
|
+
.option('--type <type>', 'Filter by type')
|
|
864
|
+
.option('--area <area>', 'Filter by area; repeatable', collectRepeatedOption, [])
|
|
865
|
+
.option('--stack <stack>', 'Filter by stack; repeatable', collectRepeatedOption, [])
|
|
866
|
+
.option('--tag <tag>', 'Filter by tag; repeatable', collectRepeatedOption, [])
|
|
867
|
+
.option('--limit <n>', 'Limit result count', parseInteger)
|
|
868
|
+
.action((options) => runExperienceSearch({ ...cmd.opts(), ...options }));
|
|
869
|
+
applyCommonOptions(search, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
870
|
+
const update = cmd.command('update')
|
|
871
|
+
.description('Patch one hosted experience item')
|
|
872
|
+
.requiredOption('--id <id>', 'Experience item id')
|
|
873
|
+
.option('--type <type>', `Experience type: ${EXPERIENCE_TYPES.join('|')}`)
|
|
874
|
+
.option('--title <title>', 'New title')
|
|
875
|
+
.option('--content <text>', 'Replace narrative notes (supports @file)')
|
|
876
|
+
.option('--problem <text>', 'Replace problem statement')
|
|
877
|
+
.option('--solution <text>', 'Replace solution')
|
|
878
|
+
.option('--area <area>', 'Replace area tags; repeatable', collectRepeatedOption, [])
|
|
879
|
+
.option('--stack <stack>', 'Replace stack/tooling tags; repeatable', collectRepeatedOption, [])
|
|
880
|
+
.option('--command <command>', 'Replace useful commands/scripts; repeatable', collectRepeatedOption, [])
|
|
881
|
+
.option('--file <path>', 'Replace relevant file paths; repeatable', collectRepeatedOption, [])
|
|
882
|
+
.option('--tag <tag>', 'Replace general tags; repeatable', collectRepeatedOption, [])
|
|
883
|
+
.option('--source-ref <json-or-ref>', 'Replace source references; repeatable', collectRepeatedOption, [])
|
|
884
|
+
.option('--confidence <level>', 'Confidence note')
|
|
885
|
+
.option('--reusability <level>', 'Reusability note')
|
|
886
|
+
.option('--preview', 'Preview the hosted update without applying')
|
|
887
|
+
.option('--apply', 'Write the item')
|
|
888
|
+
.option('--idempotency-key <key>', 'Idempotency key for the hosted write')
|
|
889
|
+
.action((options) => runExperienceUpdate({ ...cmd.opts(), ...options }));
|
|
890
|
+
applyCommonOptions(update, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
891
|
+
const del = cmd.command('delete')
|
|
892
|
+
.description('Delete one hosted experience item')
|
|
893
|
+
.requiredOption('--id <id>', 'Experience item id')
|
|
894
|
+
.option('--preview', 'Preview the hosted delete without applying')
|
|
895
|
+
.option('--apply', 'Delete the item')
|
|
896
|
+
.option('--confirm', 'Confirm destructive delete')
|
|
897
|
+
.option('--idempotency-key <key>', 'Idempotency key for the hosted delete')
|
|
898
|
+
.action((options) => runExperienceDelete({ ...cmd.opts(), ...options }));
|
|
899
|
+
applyCommonOptions(del, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
900
|
+
const promote = cmd.command('promote')
|
|
901
|
+
.description('Promote a hosted experience item into a durable memory item (or a playbook with --as-playbook)')
|
|
902
|
+
.requiredOption('--id <id>', 'Experience item id')
|
|
903
|
+
.option('--kind <kind>', 'Memory kind: note|decision (durable) or rule|constraint (with --as-playbook)')
|
|
904
|
+
.option('--durability <durability>', 'Memory durability: durable (default) or sticky')
|
|
905
|
+
.option('--content <text>', 'Reviewed promoted memory content (supports @file; defaults to experience content)')
|
|
906
|
+
.option('--tag <tag>', 'Extra memory tag; repeatable', collectRepeatedOption, [])
|
|
907
|
+
.option('--as-playbook', 'Promote as a reviewed playbook rule/constraint memory item')
|
|
908
|
+
.option('--playbook-id <id>', 'Stable playbook id (default: experience id)')
|
|
909
|
+
.option('--playbook-scope <scope>', 'Playbook scope: session or project')
|
|
910
|
+
.option('--playbook-area <area>', 'Playbook area tag, such as backend or hexagen')
|
|
911
|
+
.option('--applies-when <text>', 'When this playbook should be applied')
|
|
912
|
+
.option('--step <text>', 'Playbook step; repeatable', collectRepeatedOption, [])
|
|
913
|
+
.option('--enforcement <level>', 'Playbook enforcement: advisory | soft-preflight | strict-opt-in')
|
|
914
|
+
.option('--review-state <state>', 'Playbook review state: proposed | accepted | superseded | archived')
|
|
915
|
+
.option('--supersedes <id>', 'Older playbook id this one supersedes')
|
|
916
|
+
.option('--preview', 'Preview the hosted promotion without applying')
|
|
917
|
+
.option('--apply', 'Write the memory item')
|
|
918
|
+
.option('--idempotency-key <key>', 'Idempotency key for the hosted write')
|
|
919
|
+
.action((options) => runExperiencePromote({ ...cmd.opts(), ...options }));
|
|
920
|
+
applyCommonOptions(promote, { withAuth: false, withProject: true, withYes: true, withJson: true });
|
|
921
|
+
return cmd;
|
|
922
|
+
}
|