@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,1363 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } 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 { invokeHostedToolWithApiState, parseJsonInput, requireApiState, unwrapHostedToolResult, } from '../utils/agent-gateway.js';
|
|
6
|
+
import { preferProjectNameBinding, resolveOwnerScopeIdFromBinding, resolveOwnerScopeIdFromProjectRecord, resolveProjectBindingContext, } from '../utils/project-context.js';
|
|
7
|
+
import { buildHostedSugarEnvelope, buildOperatorCookbook, ensureGuardedWrite, missingScopeIdMessage, } from '../utils/hosted-sugar.js';
|
|
8
|
+
import { GUIDE_PATHS } from '../utils/guide-paths.js';
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function unwrapResultData(result) {
|
|
13
|
+
if (isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data')) {
|
|
14
|
+
return result.data;
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
function collectRepeatedOption(value, previous = []) {
|
|
19
|
+
const normalized = normalizeNonEmpty(value);
|
|
20
|
+
return normalized ? [...previous, normalized] : previous;
|
|
21
|
+
}
|
|
22
|
+
function toInteger(value, label) {
|
|
23
|
+
if (typeof value === 'number' && Number.isInteger(value))
|
|
24
|
+
return value;
|
|
25
|
+
const normalized = normalizeNonEmpty(value);
|
|
26
|
+
if (!normalized)
|
|
27
|
+
throw new Error(`${label} must be an integer.`);
|
|
28
|
+
const parsed = Number.parseInt(normalized, 10);
|
|
29
|
+
if (!Number.isInteger(parsed))
|
|
30
|
+
throw new Error(`${label} must be an integer.`);
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
function toStringArray(values) {
|
|
34
|
+
return Array.isArray(values)
|
|
35
|
+
? values.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
|
|
36
|
+
: [];
|
|
37
|
+
}
|
|
38
|
+
function parseJsonSeed(input, label = '--input') {
|
|
39
|
+
const normalized = normalizeNonEmpty(input);
|
|
40
|
+
if (!normalized)
|
|
41
|
+
return {};
|
|
42
|
+
const parsed = parseJsonInput(normalized, label);
|
|
43
|
+
if (!isRecord(parsed))
|
|
44
|
+
throw new Error(`${label} must be a JSON object or @file.json object.`);
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
function parseJsonObjectInput(input, label) {
|
|
48
|
+
const normalized = normalizeNonEmpty(input);
|
|
49
|
+
if (!normalized)
|
|
50
|
+
return undefined;
|
|
51
|
+
const parsed = parseJsonInput(normalized, label);
|
|
52
|
+
if (!isRecord(parsed))
|
|
53
|
+
throw new Error(`${label} must be a JSON object or @file.json object.`);
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
function readTextInput(input) {
|
|
57
|
+
if (typeof input !== 'string')
|
|
58
|
+
return undefined;
|
|
59
|
+
const trimmed = input.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (trimmed.startsWith('@')) {
|
|
63
|
+
const filePath = trimmed.slice(1).trim();
|
|
64
|
+
if (!filePath)
|
|
65
|
+
throw new Error('Expected a file path after @.');
|
|
66
|
+
return readFileSync(filePath, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
return input;
|
|
69
|
+
}
|
|
70
|
+
function seedSection(seed, key) {
|
|
71
|
+
const nested = seed[key];
|
|
72
|
+
return isRecord(nested) ? nested : seed;
|
|
73
|
+
}
|
|
74
|
+
function resolveStringField(explicit, seed, key) {
|
|
75
|
+
return normalizeNonEmpty(explicit) ?? normalizeNonEmpty(seed[key]);
|
|
76
|
+
}
|
|
77
|
+
function resolveTextField(explicit, seed, key) {
|
|
78
|
+
const explicitValue = readTextInput(explicit);
|
|
79
|
+
if (explicitValue !== undefined)
|
|
80
|
+
return explicitValue;
|
|
81
|
+
return typeof seed[key] === 'string' ? seed[key] : undefined;
|
|
82
|
+
}
|
|
83
|
+
function resolveIntegerField(explicit, seed, key, label) {
|
|
84
|
+
if (explicit !== undefined && explicit !== null && explicit !== '')
|
|
85
|
+
return toInteger(explicit, label);
|
|
86
|
+
if (seed[key] !== undefined && seed[key] !== null && seed[key] !== '')
|
|
87
|
+
return toInteger(seed[key], label);
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function resolveStringArrayField(explicit, seed, key) {
|
|
91
|
+
const explicitValues = toStringArray(explicit);
|
|
92
|
+
if (explicitValues.length > 0)
|
|
93
|
+
return explicitValues;
|
|
94
|
+
const seededValues = toStringArray(seed[key]);
|
|
95
|
+
return seededValues.length > 0 ? seededValues : undefined;
|
|
96
|
+
}
|
|
97
|
+
function buildGatewayOptions(options, resolvedContext) {
|
|
98
|
+
return {
|
|
99
|
+
...options,
|
|
100
|
+
...preferProjectNameBinding(resolvedContext),
|
|
101
|
+
projectId: normalizeNonEmpty(options.projectId) ?? normalizeNonEmpty(resolvedContext.projectId),
|
|
102
|
+
scopeId: normalizeNonEmpty(options.scopeId) ?? normalizeNonEmpty(resolvedContext.scopeId),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function hydrateProjectContext(apiState, options, resolvedContext) {
|
|
106
|
+
const projectId = normalizeNonEmpty(resolvedContext.projectId);
|
|
107
|
+
if (!projectId)
|
|
108
|
+
return resolvedContext;
|
|
109
|
+
if (normalizeNonEmpty(options.scopeId))
|
|
110
|
+
return resolvedContext;
|
|
111
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
112
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
113
|
+
toolId: 'agentspace.project.get-by-id',
|
|
114
|
+
input: { id: projectId },
|
|
115
|
+
});
|
|
116
|
+
const result = unwrapHostedToolResult(payload);
|
|
117
|
+
const project = unwrapResultData(result);
|
|
118
|
+
if (!isRecord(project))
|
|
119
|
+
return resolvedContext;
|
|
120
|
+
return {
|
|
121
|
+
...resolvedContext,
|
|
122
|
+
scopeId: resolveOwnerScopeIdFromProjectRecord(project, resolvedContext.scopeId),
|
|
123
|
+
projectName: normalizeNonEmpty(project.name) ?? resolvedContext.projectName,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function resolveChatContext(options, apiState) {
|
|
127
|
+
const resolved = await resolveProjectBindingContext(options, {
|
|
128
|
+
requireProject: false,
|
|
129
|
+
});
|
|
130
|
+
const scopeId = normalizeNonEmpty(options.scopeId) ?? resolveOwnerScopeIdFromBinding(resolved);
|
|
131
|
+
return hydrateProjectContext(apiState, options, {
|
|
132
|
+
...resolved,
|
|
133
|
+
scopeId,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function buildResolvedContextRecord(context) {
|
|
137
|
+
return compactPayload({
|
|
138
|
+
repoRoot: context.repoRoot,
|
|
139
|
+
configPath: context.configPath,
|
|
140
|
+
configFound: context.configFound,
|
|
141
|
+
scopeId: context.scopeId,
|
|
142
|
+
projectId: context.projectId,
|
|
143
|
+
projectName: context.projectName,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const buildEnvelope = buildHostedSugarEnvelope;
|
|
147
|
+
function requireScopeId(context, seed, subject) {
|
|
148
|
+
const scopeId = normalizeNonEmpty(context.scopeId) ?? normalizeNonEmpty(seed.scopeId);
|
|
149
|
+
if (!scopeId)
|
|
150
|
+
throw new Error(missingScopeIdMessage(subject));
|
|
151
|
+
return scopeId;
|
|
152
|
+
}
|
|
153
|
+
function requireField(value, label) {
|
|
154
|
+
const normalized = normalizeNonEmpty(value);
|
|
155
|
+
if (!normalized)
|
|
156
|
+
throw new Error(`Provide ${label}.`);
|
|
157
|
+
return normalized;
|
|
158
|
+
}
|
|
159
|
+
function parseMemberSpec(spec) {
|
|
160
|
+
const normalized = requireField(spec, '--member');
|
|
161
|
+
if (normalized.startsWith('{') || normalized.startsWith('@')) {
|
|
162
|
+
const parsed = parseJsonObjectInput(normalized, '--member');
|
|
163
|
+
if (!parsed)
|
|
164
|
+
throw new Error('--member JSON must be an object.');
|
|
165
|
+
return parsed;
|
|
166
|
+
}
|
|
167
|
+
const [agentId, roleKey] = normalized.split(':', 2);
|
|
168
|
+
return compactPayload({
|
|
169
|
+
agentId: requireField(agentId, '--member agentId'),
|
|
170
|
+
roleKey: normalizeNonEmpty(roleKey),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function parseKeyValueList(input) {
|
|
174
|
+
const result = {};
|
|
175
|
+
input.split(',').forEach((part) => {
|
|
176
|
+
const separator = part.indexOf('=');
|
|
177
|
+
if (separator < 1)
|
|
178
|
+
throw new Error(`Expected key=value in "${part}".`);
|
|
179
|
+
const key = part.slice(0, separator).trim();
|
|
180
|
+
const value = part.slice(separator + 1).trim();
|
|
181
|
+
if (!key || !value)
|
|
182
|
+
throw new Error(`Expected non-empty key=value in "${part}".`);
|
|
183
|
+
result[key] = value;
|
|
184
|
+
});
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
function parseBindingSpec(spec) {
|
|
188
|
+
const normalized = requireField(spec, '--binding');
|
|
189
|
+
if (normalized.startsWith('{') || normalized.startsWith('@')) {
|
|
190
|
+
const parsed = parseJsonObjectInput(normalized, '--binding');
|
|
191
|
+
if (!parsed)
|
|
192
|
+
throw new Error('--binding JSON must be an object.');
|
|
193
|
+
return parsed;
|
|
194
|
+
}
|
|
195
|
+
return parseKeyValueList(normalized);
|
|
196
|
+
}
|
|
197
|
+
function parseRoleSpecs(values = []) {
|
|
198
|
+
if (values.length === 0)
|
|
199
|
+
return undefined;
|
|
200
|
+
const roles = {};
|
|
201
|
+
values.forEach((entry) => {
|
|
202
|
+
const parsed = parseKeyValueList(entry);
|
|
203
|
+
Object.assign(roles, parsed);
|
|
204
|
+
});
|
|
205
|
+
return roles;
|
|
206
|
+
}
|
|
207
|
+
function mergeObjectArrays(seedValue, explicitValue, parser) {
|
|
208
|
+
const seeded = Array.isArray(seedValue)
|
|
209
|
+
? seedValue.filter((entry) => isRecord(entry))
|
|
210
|
+
: [];
|
|
211
|
+
const explicit = (explicitValue ?? []).map(parser);
|
|
212
|
+
const merged = [...seeded, ...explicit];
|
|
213
|
+
return merged.length > 0 ? merged : undefined;
|
|
214
|
+
}
|
|
215
|
+
function collectChatArtifacts(result) {
|
|
216
|
+
const root = unwrapResultData(result) ?? (isRecord(result) ? result : {});
|
|
217
|
+
const artifacts = {};
|
|
218
|
+
const push = (key, value) => {
|
|
219
|
+
const normalized = normalizeNonEmpty(value);
|
|
220
|
+
if (normalized)
|
|
221
|
+
artifacts[key] = normalized;
|
|
222
|
+
};
|
|
223
|
+
push('roomId', root.roomId);
|
|
224
|
+
if (!artifacts.roomId && (normalizeNonEmpty(root.slug) || normalizeNonEmpty(root.title) || normalizeNonEmpty(root.dmKey))) {
|
|
225
|
+
push('roomId', root.id);
|
|
226
|
+
}
|
|
227
|
+
if (normalizeNonEmpty(root.agentId) && normalizeNonEmpty(root.roomId))
|
|
228
|
+
push('memberId', root.id);
|
|
229
|
+
if (normalizeNonEmpty(root.bindingType))
|
|
230
|
+
push('bindingId', root.id);
|
|
231
|
+
if (normalizeNonEmpty(root.text) || Number.isInteger(root.seq))
|
|
232
|
+
push('messageId', root.id);
|
|
233
|
+
if (Number.isInteger(root.seq))
|
|
234
|
+
artifacts.seq = String(root.seq);
|
|
235
|
+
return Object.keys(artifacts).length > 0 ? artifacts : undefined;
|
|
236
|
+
}
|
|
237
|
+
function summarizeText(value, maxLength = 160) {
|
|
238
|
+
const text = normalizeNonEmpty(value);
|
|
239
|
+
if (!text)
|
|
240
|
+
return undefined;
|
|
241
|
+
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 3)}...`;
|
|
242
|
+
}
|
|
243
|
+
function summarizeRoom(value) {
|
|
244
|
+
if (!isRecord(value))
|
|
245
|
+
return value;
|
|
246
|
+
return compactPayload({
|
|
247
|
+
id: normalizeNonEmpty(value.id),
|
|
248
|
+
scopeId: normalizeNonEmpty(value.scopeId),
|
|
249
|
+
projectId: normalizeNonEmpty(value.projectId),
|
|
250
|
+
slug: normalizeNonEmpty(value.slug),
|
|
251
|
+
title: normalizeNonEmpty(value.title),
|
|
252
|
+
kind: normalizeNonEmpty(value.kind),
|
|
253
|
+
status: normalizeNonEmpty(value.status),
|
|
254
|
+
purpose: summarizeText(value.purpose),
|
|
255
|
+
lastSeq: value.lastSeq,
|
|
256
|
+
dmKey: normalizeNonEmpty(value.dmKey),
|
|
257
|
+
createdBy: normalizeNonEmpty(value.createdBy),
|
|
258
|
+
updatedBy: normalizeNonEmpty(value.updatedBy),
|
|
259
|
+
createdAt: normalizeNonEmpty(value.createdAt),
|
|
260
|
+
updatedAt: normalizeNonEmpty(value.updatedAt),
|
|
261
|
+
archivedAt: normalizeNonEmpty(value.archivedAt),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function summarizeMember(value) {
|
|
265
|
+
if (!isRecord(value))
|
|
266
|
+
return value;
|
|
267
|
+
return compactPayload({
|
|
268
|
+
id: normalizeNonEmpty(value.id),
|
|
269
|
+
roomId: normalizeNonEmpty(value.roomId),
|
|
270
|
+
agentId: normalizeNonEmpty(value.agentId),
|
|
271
|
+
roleKey: normalizeNonEmpty(value.roleKey),
|
|
272
|
+
brief: summarizeText(value.brief),
|
|
273
|
+
status: normalizeNonEmpty(value.status),
|
|
274
|
+
lastReadSeq: value.lastReadSeq,
|
|
275
|
+
joinedAt: normalizeNonEmpty(value.joinedAt),
|
|
276
|
+
leftAt: normalizeNonEmpty(value.leftAt),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function summarizeBinding(value) {
|
|
280
|
+
if (!isRecord(value))
|
|
281
|
+
return value;
|
|
282
|
+
return compactPayload({
|
|
283
|
+
id: normalizeNonEmpty(value.id),
|
|
284
|
+
roomId: normalizeNonEmpty(value.roomId),
|
|
285
|
+
bindingType: normalizeNonEmpty(value.bindingType),
|
|
286
|
+
refId: normalizeNonEmpty(value.refId),
|
|
287
|
+
uri: normalizeNonEmpty(value.uri),
|
|
288
|
+
title: normalizeNonEmpty(value.title),
|
|
289
|
+
note: summarizeText(value.note),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function summarizeMessage(value) {
|
|
293
|
+
if (!isRecord(value))
|
|
294
|
+
return value;
|
|
295
|
+
return compactPayload({
|
|
296
|
+
id: normalizeNonEmpty(value.id),
|
|
297
|
+
roomId: normalizeNonEmpty(value.roomId),
|
|
298
|
+
seq: value.seq,
|
|
299
|
+
authorAgentId: normalizeNonEmpty(value.authorAgentId),
|
|
300
|
+
kind: normalizeNonEmpty(value.kind),
|
|
301
|
+
text: summarizeText(value.text, 220),
|
|
302
|
+
mentions: Array.isArray(value.mentions) ? value.mentions : undefined,
|
|
303
|
+
replyToSeq: value.replyToSeq,
|
|
304
|
+
idempotencyKey: normalizeNonEmpty(value.idempotencyKey),
|
|
305
|
+
createdAt: normalizeNonEmpty(value.createdAt),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function summarizeArrayResult(result, mapper, mode, hint) {
|
|
309
|
+
if (Array.isArray(result)) {
|
|
310
|
+
return {
|
|
311
|
+
data: result.map(mapper),
|
|
312
|
+
summary: { mode, count: result.length, fullRecordHint: hint },
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (isRecord(result) && Array.isArray(result.data)) {
|
|
316
|
+
return {
|
|
317
|
+
...result,
|
|
318
|
+
data: result.data.map(mapper),
|
|
319
|
+
summary: {
|
|
320
|
+
...(isRecord(result.summary) ? result.summary : {}),
|
|
321
|
+
mode,
|
|
322
|
+
count: result.data.length,
|
|
323
|
+
fullRecordHint: hint,
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function summarizeManifestResult(result) {
|
|
330
|
+
const manifest = unwrapResultData(result);
|
|
331
|
+
if (!isRecord(manifest))
|
|
332
|
+
return result;
|
|
333
|
+
return compactPayload({
|
|
334
|
+
...manifest,
|
|
335
|
+
room: summarizeRoom(manifest.room),
|
|
336
|
+
members: Array.isArray(manifest.members) ? manifest.members.map(summarizeMember) : undefined,
|
|
337
|
+
bindings: Array.isArray(manifest.bindings) ? manifest.bindings.map(summarizeBinding) : undefined,
|
|
338
|
+
messages: Array.isArray(manifest.messages) ? manifest.messages.map(summarizeMessage) : undefined,
|
|
339
|
+
summary: {
|
|
340
|
+
mode: 'chat-room-manifest-summary',
|
|
341
|
+
memberCount: Array.isArray(manifest.members) ? manifest.members.length : 0,
|
|
342
|
+
bindingCount: Array.isArray(manifest.bindings) ? manifest.bindings.length : 0,
|
|
343
|
+
messageCount: Array.isArray(manifest.messages) ? manifest.messages.length : undefined,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function summarizeCatchupResult(result) {
|
|
348
|
+
const catchup = unwrapResultData(result);
|
|
349
|
+
if (!isRecord(catchup))
|
|
350
|
+
return result;
|
|
351
|
+
const rooms = Array.isArray(catchup.rooms)
|
|
352
|
+
? catchup.rooms.map((entry) => isRecord(entry)
|
|
353
|
+
? compactPayload({
|
|
354
|
+
room: summarizeRoom(entry.room),
|
|
355
|
+
member: summarizeMember(entry.member),
|
|
356
|
+
messages: Array.isArray(entry.messages) ? entry.messages.map(summarizeMessage) : undefined,
|
|
357
|
+
unreadCount: entry.unreadCount,
|
|
358
|
+
})
|
|
359
|
+
: entry)
|
|
360
|
+
: undefined;
|
|
361
|
+
return compactPayload({
|
|
362
|
+
...catchup,
|
|
363
|
+
rooms,
|
|
364
|
+
summary: {
|
|
365
|
+
mode: 'chat-catchup-summary',
|
|
366
|
+
roomCount: rooms?.length ?? 0,
|
|
367
|
+
unreadCount: catchup.unreadCount,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function catchupData(result) {
|
|
372
|
+
const data = unwrapResultData(result);
|
|
373
|
+
return isRecord(data) ? data : undefined;
|
|
374
|
+
}
|
|
375
|
+
function catchupRooms(result) {
|
|
376
|
+
const data = catchupData(result);
|
|
377
|
+
return Array.isArray(data?.rooms)
|
|
378
|
+
? data.rooms.filter((entry) => isRecord(entry))
|
|
379
|
+
: [];
|
|
380
|
+
}
|
|
381
|
+
function maxMessageSeq(messages) {
|
|
382
|
+
if (!Array.isArray(messages))
|
|
383
|
+
return undefined;
|
|
384
|
+
let max;
|
|
385
|
+
for (const message of messages) {
|
|
386
|
+
if (!isRecord(message))
|
|
387
|
+
continue;
|
|
388
|
+
const seq = typeof message.seq === 'number' ? message.seq : undefined;
|
|
389
|
+
if (seq !== undefined && Number.isInteger(seq) && (max === undefined || seq > max))
|
|
390
|
+
max = seq;
|
|
391
|
+
}
|
|
392
|
+
return max;
|
|
393
|
+
}
|
|
394
|
+
function catchupHasUnread(result) {
|
|
395
|
+
const data = catchupData(result);
|
|
396
|
+
const unreadCount = typeof data?.unreadCount === 'number' ? data.unreadCount : 0;
|
|
397
|
+
if (unreadCount > 0)
|
|
398
|
+
return true;
|
|
399
|
+
return catchupRooms(result).some(catchupRoomHasUnread);
|
|
400
|
+
}
|
|
401
|
+
function catchupRoomHasUnread(entry) {
|
|
402
|
+
const unreadCount = typeof entry.unreadCount === 'number' ? entry.unreadCount : 0;
|
|
403
|
+
return unreadCount > 0;
|
|
404
|
+
}
|
|
405
|
+
function catchupUnreadCountFromRooms(rooms) {
|
|
406
|
+
let total = 0;
|
|
407
|
+
for (const room of rooms) {
|
|
408
|
+
const unreadCount = typeof room.unreadCount === 'number' ? room.unreadCount : undefined;
|
|
409
|
+
if (unreadCount !== undefined && unreadCount > 0) {
|
|
410
|
+
total += unreadCount;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return total;
|
|
414
|
+
}
|
|
415
|
+
function isTerminalCatchupRoom(entry) {
|
|
416
|
+
const room = isRecord(entry.room) ? entry.room : {};
|
|
417
|
+
const member = isRecord(entry.member) ? entry.member : {};
|
|
418
|
+
return normalizeNonEmpty(room.status) === 'archived' || normalizeNonEmpty(member.status) === 'left';
|
|
419
|
+
}
|
|
420
|
+
function catchupHasTerminalRoom(result) {
|
|
421
|
+
return catchupRooms(result).some(isTerminalCatchupRoom);
|
|
422
|
+
}
|
|
423
|
+
function filterTerminalCatchupRooms(result) {
|
|
424
|
+
const data = catchupData(result);
|
|
425
|
+
if (!data)
|
|
426
|
+
return result;
|
|
427
|
+
const rooms = catchupRooms(result);
|
|
428
|
+
const activeRooms = rooms.filter((entry) => !isTerminalCatchupRoom(entry));
|
|
429
|
+
if (activeRooms.length === rooms.length)
|
|
430
|
+
return result;
|
|
431
|
+
const filteredData = compactPayload({
|
|
432
|
+
...data,
|
|
433
|
+
rooms: activeRooms,
|
|
434
|
+
unreadCount: catchupUnreadCountFromRooms(activeRooms),
|
|
435
|
+
});
|
|
436
|
+
if (isRecord(result) && Object.prototype.hasOwnProperty.call(result, 'data')) {
|
|
437
|
+
return { ...result, data: filteredData };
|
|
438
|
+
}
|
|
439
|
+
return filteredData;
|
|
440
|
+
}
|
|
441
|
+
function plannedCursorUpdates(result, agentId) {
|
|
442
|
+
const updates = [];
|
|
443
|
+
for (const entry of catchupRooms(result)) {
|
|
444
|
+
const room = isRecord(entry.room) ? entry.room : {};
|
|
445
|
+
const roomId = normalizeNonEmpty(room.id) ?? normalizeNonEmpty(room.roomId) ?? normalizeNonEmpty(entry.roomId);
|
|
446
|
+
const seq = maxMessageSeq(entry.messages);
|
|
447
|
+
if (!roomId || seq === undefined)
|
|
448
|
+
continue;
|
|
449
|
+
updates.push({ roomId, agentId, seq });
|
|
450
|
+
}
|
|
451
|
+
return updates;
|
|
452
|
+
}
|
|
453
|
+
function buildRoomBriefMarkdown(result, options = {}) {
|
|
454
|
+
const manifest = unwrapResultData(result);
|
|
455
|
+
const room = isRecord(manifest?.room) ? manifest.room : {};
|
|
456
|
+
const members = Array.isArray(manifest?.members) ? manifest.members.filter((entry) => isRecord(entry)) : [];
|
|
457
|
+
const bindings = Array.isArray(manifest?.bindings) ? manifest.bindings.filter((entry) => isRecord(entry)) : [];
|
|
458
|
+
const title = normalizeNonEmpty(room.title) ?? normalizeNonEmpty(room.slug) ?? normalizeNonEmpty(room.id) ?? 'Chat Room';
|
|
459
|
+
const lines = [
|
|
460
|
+
`# ${title}`,
|
|
461
|
+
'',
|
|
462
|
+
`Room id: ${normalizeNonEmpty(room.id) ?? '(unknown)'}`,
|
|
463
|
+
];
|
|
464
|
+
const forAgent = normalizeNonEmpty(options.forAgent);
|
|
465
|
+
if (forAgent)
|
|
466
|
+
lines.push(`Audience: ${forAgent}`);
|
|
467
|
+
const purpose = normalizeNonEmpty(room.purpose);
|
|
468
|
+
if (purpose)
|
|
469
|
+
lines.push('', '## Purpose', purpose);
|
|
470
|
+
const guidance = normalizeNonEmpty(room.guidanceMarkdown);
|
|
471
|
+
if (guidance)
|
|
472
|
+
lines.push('', '## Guidance', guidance);
|
|
473
|
+
lines.push('', '## Members');
|
|
474
|
+
if (members.length === 0) {
|
|
475
|
+
lines.push('- (none listed)');
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
members.forEach((member) => {
|
|
479
|
+
const agentId = normalizeNonEmpty(member.agentId) ?? '(unknown-agent)';
|
|
480
|
+
const role = normalizeNonEmpty(member.roleKey);
|
|
481
|
+
const brief = normalizeNonEmpty(member.brief);
|
|
482
|
+
lines.push(`- ${agentId}${role ? ` (${role})` : ''}${brief ? `: ${brief}` : ''}`);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
lines.push('', '## Bindings');
|
|
486
|
+
if (bindings.length === 0) {
|
|
487
|
+
lines.push('- (none listed)');
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
bindings.forEach((binding) => {
|
|
491
|
+
const type = normalizeNonEmpty(binding.bindingType) ?? 'binding';
|
|
492
|
+
const titleOrRef = normalizeNonEmpty(binding.title) ?? normalizeNonEmpty(binding.refId) ?? normalizeNonEmpty(binding.uri) ?? '(unlabeled)';
|
|
493
|
+
const uri = normalizeNonEmpty(binding.uri);
|
|
494
|
+
const note = normalizeNonEmpty(binding.note);
|
|
495
|
+
lines.push(`- ${type}: ${titleOrRef}${uri && uri !== titleOrRef ? ` <${uri}>` : ''}${note ? ` - ${note}` : ''}`);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
lines.push('', '## Join Prompt');
|
|
499
|
+
lines.push('Use this room brief as the current shared context. Follow the room guidance, respect the member roles, and reference the bindings before taking action.');
|
|
500
|
+
return `${lines.join('\n')}\n`;
|
|
501
|
+
}
|
|
502
|
+
function buildCatchupInput(options) {
|
|
503
|
+
return {
|
|
504
|
+
data: compactPayload({
|
|
505
|
+
roomId: normalizeNonEmpty(options.roomId),
|
|
506
|
+
agentId: requireField(options.for, '--for'),
|
|
507
|
+
limit: options.limit !== undefined ? toInteger(options.limit, '--limit') : undefined,
|
|
508
|
+
}),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
async function invokeChatRaw(apiState, options, resolvedContext, params) {
|
|
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: params.toolId,
|
|
522
|
+
input: params.input,
|
|
523
|
+
preview: params.preview,
|
|
524
|
+
apply: params.apply,
|
|
525
|
+
idempotencyKey: params.idempotencyKey,
|
|
526
|
+
});
|
|
527
|
+
return unwrapHostedToolResult(payload);
|
|
528
|
+
}
|
|
529
|
+
async function applyCursorUpdates(apiState, options, resolvedContext, updates) {
|
|
530
|
+
const results = [];
|
|
531
|
+
for (const update of updates) {
|
|
532
|
+
const idempotencyKey = normalizeNonEmpty(options.idempotencyKey)
|
|
533
|
+
? `${options.idempotencyKey}:mark-read:${update.roomId}:${update.seq}`
|
|
534
|
+
: undefined;
|
|
535
|
+
results.push(await invokeChatRaw(apiState, options, resolvedContext, {
|
|
536
|
+
toolId: 'agentspace.chat.mark-read',
|
|
537
|
+
input: { data: update },
|
|
538
|
+
apply: options.apply,
|
|
539
|
+
idempotencyKey,
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
return results;
|
|
543
|
+
}
|
|
544
|
+
function writeChatEnvelope(params) {
|
|
545
|
+
if (params.options.json) {
|
|
546
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
547
|
+
command: params.command,
|
|
548
|
+
toolId: params.toolId,
|
|
549
|
+
surface: 'agentspace-chat-v1',
|
|
550
|
+
resolvedContext: buildResolvedContextRecord(params.resolvedContext),
|
|
551
|
+
input: params.input,
|
|
552
|
+
artifacts: params.artifacts,
|
|
553
|
+
result: params.result,
|
|
554
|
+
}), null, 2));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
logSuccess(params.successText);
|
|
558
|
+
console.log(JSON.stringify(params.result, null, 2));
|
|
559
|
+
}
|
|
560
|
+
function listenTimeoutMs(value, fallback, label) {
|
|
561
|
+
if (value === undefined)
|
|
562
|
+
return fallback;
|
|
563
|
+
const parsed = toInteger(value, label);
|
|
564
|
+
if (parsed < 0)
|
|
565
|
+
throw new Error(`${label} must be non-negative.`);
|
|
566
|
+
return parsed;
|
|
567
|
+
}
|
|
568
|
+
function isTerminalListenError(error) {
|
|
569
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
570
|
+
const normalized = message.toLowerCase();
|
|
571
|
+
return normalized.includes('not_found') || normalized.includes('record not found');
|
|
572
|
+
}
|
|
573
|
+
function delay(ms) {
|
|
574
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
575
|
+
}
|
|
576
|
+
async function invokeChatTool(apiState, options, resolvedContext, params) {
|
|
577
|
+
if (params.write)
|
|
578
|
+
ensureGuardedWrite(options, 'This command mutates hosted chat state.');
|
|
579
|
+
const payload = await invokeHostedToolWithApiState(apiState, {
|
|
580
|
+
...buildGatewayOptions(options, resolvedContext),
|
|
581
|
+
tenantId: options.tenantId,
|
|
582
|
+
locale: options.locale,
|
|
583
|
+
fallbackLocale: options.fallbackLocale,
|
|
584
|
+
timeoutMs: options.timeoutMs,
|
|
585
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
586
|
+
accessToken: options.accessToken,
|
|
587
|
+
refreshToken: options.refreshToken,
|
|
588
|
+
toolId: params.toolId,
|
|
589
|
+
input: params.input,
|
|
590
|
+
preview: options.preview,
|
|
591
|
+
apply: options.apply,
|
|
592
|
+
confirm: options.confirm,
|
|
593
|
+
idempotencyKey: options.idempotencyKey,
|
|
594
|
+
});
|
|
595
|
+
const rawResult = unwrapHostedToolResult(payload);
|
|
596
|
+
const result = params.resultMapper ? params.resultMapper(rawResult) : rawResult;
|
|
597
|
+
if (params.out) {
|
|
598
|
+
writeFileSync(params.out, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
599
|
+
}
|
|
600
|
+
if (options.json) {
|
|
601
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
602
|
+
command: params.command,
|
|
603
|
+
toolId: params.toolId,
|
|
604
|
+
surface: 'agentspace-chat-v1',
|
|
605
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
606
|
+
input: params.input,
|
|
607
|
+
artifacts: collectChatArtifacts(rawResult),
|
|
608
|
+
result,
|
|
609
|
+
}), null, 2));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
logSuccess(params.successText);
|
|
613
|
+
console.log(JSON.stringify(result, null, 2));
|
|
614
|
+
}
|
|
615
|
+
async function withChatContext(options, body) {
|
|
616
|
+
try {
|
|
617
|
+
const apiState = await requireApiState(options);
|
|
618
|
+
if (!apiState)
|
|
619
|
+
return;
|
|
620
|
+
const resolvedContext = await resolveChatContext(options, apiState);
|
|
621
|
+
await body(apiState, resolvedContext);
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
625
|
+
process.exitCode = 1;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
export async function runChatRoomList(options = {}) {
|
|
629
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
630
|
+
const input = {
|
|
631
|
+
filter: compactPayload({
|
|
632
|
+
scopeId: normalizeNonEmpty(resolvedContext.scopeId),
|
|
633
|
+
scopeResolution: normalizeNonEmpty(options.scopeResolution),
|
|
634
|
+
projectId: normalizeNonEmpty(options.projectId) ?? normalizeNonEmpty(resolvedContext.projectId),
|
|
635
|
+
slug: normalizeNonEmpty(options.slug),
|
|
636
|
+
title: normalizeNonEmpty(options.title),
|
|
637
|
+
kind: normalizeNonEmpty(options.kind),
|
|
638
|
+
status: normalizeNonEmpty(options.status),
|
|
639
|
+
}),
|
|
640
|
+
options: compactPayload({
|
|
641
|
+
limit: options.limit !== undefined ? toInteger(options.limit, '--limit') : undefined,
|
|
642
|
+
}),
|
|
643
|
+
};
|
|
644
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
645
|
+
command: 'chat.room.list',
|
|
646
|
+
toolId: 'agentspace.chat-room.list',
|
|
647
|
+
input,
|
|
648
|
+
successText: 'Chat rooms loaded.',
|
|
649
|
+
resultMapper: options.summary
|
|
650
|
+
? (result) => summarizeArrayResult(result, summarizeRoom, 'chat-room-list-summary', 'Use `aops-cli chat room get --id <room-id> --json` for a full room record.')
|
|
651
|
+
: undefined,
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
export async function runChatRoomGet(options = {}) {
|
|
656
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
657
|
+
const input = { id: requireField(options.id, '--id') };
|
|
658
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
659
|
+
command: 'chat.room.get',
|
|
660
|
+
toolId: 'agentspace.chat-room.get-by-id',
|
|
661
|
+
input,
|
|
662
|
+
successText: 'Chat room loaded.',
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
export async function runChatRoomCreate(options = {}) {
|
|
667
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
668
|
+
const seed = seedSection(parseJsonSeed(options.input), 'data');
|
|
669
|
+
const members = mergeObjectArrays(seed.members, options.member, parseMemberSpec);
|
|
670
|
+
const bindings = mergeObjectArrays(seed.bindings, options.binding, parseBindingSpec);
|
|
671
|
+
const createdBy = resolveStringField(options.createdBy, seed, 'createdBy');
|
|
672
|
+
if (!createdBy && (!members || members.length === 0)) {
|
|
673
|
+
throw new Error('Chat room create requires --created-by or at least one --member / input.members entry.');
|
|
674
|
+
}
|
|
675
|
+
const input = {
|
|
676
|
+
data: compactPayload({
|
|
677
|
+
scopeId: requireScopeId(resolvedContext, seed, 'Chat room create'),
|
|
678
|
+
projectId: normalizeNonEmpty(options.projectId) ?? normalizeNonEmpty(resolvedContext.projectId) ?? normalizeNonEmpty(seed.projectId),
|
|
679
|
+
slug: requireField(resolveStringField(options.slug, seed, 'slug'), '--slug'),
|
|
680
|
+
title: requireField(resolveStringField(options.title, seed, 'title'), '--title'),
|
|
681
|
+
kind: resolveStringField(options.kind, seed, 'kind'),
|
|
682
|
+
purpose: resolveStringField(options.purpose, seed, 'purpose'),
|
|
683
|
+
guidanceMarkdown: resolveTextField(options.guidance, seed, 'guidanceMarkdown'),
|
|
684
|
+
status: resolveStringField(options.status, seed, 'status'),
|
|
685
|
+
createdBy,
|
|
686
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
687
|
+
members,
|
|
688
|
+
bindings,
|
|
689
|
+
}),
|
|
690
|
+
};
|
|
691
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
692
|
+
command: 'chat.room.create',
|
|
693
|
+
toolId: 'agentspace.chat-room.create',
|
|
694
|
+
input,
|
|
695
|
+
successText: 'Chat room created.',
|
|
696
|
+
write: true,
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
export async function runChatRoomUpdate(options = {}) {
|
|
701
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
702
|
+
const rawSeed = parseJsonSeed(options.input);
|
|
703
|
+
const seed = seedSection(rawSeed, 'patch');
|
|
704
|
+
const patch = compactPayload({
|
|
705
|
+
title: resolveStringField(options.title, seed, 'title'),
|
|
706
|
+
purpose: resolveStringField(options.purpose, seed, 'purpose'),
|
|
707
|
+
guidanceMarkdown: resolveTextField(options.guidance, seed, 'guidanceMarkdown'),
|
|
708
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
709
|
+
});
|
|
710
|
+
if (Object.keys(patch).length === 0)
|
|
711
|
+
throw new Error('Provide at least one chat room field to update.');
|
|
712
|
+
const input = {
|
|
713
|
+
id: requireField(resolveStringField(options.id, rawSeed, 'id'), '--id'),
|
|
714
|
+
patch,
|
|
715
|
+
};
|
|
716
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
717
|
+
command: 'chat.room.update',
|
|
718
|
+
toolId: 'agentspace.chat-room.update',
|
|
719
|
+
input,
|
|
720
|
+
successText: 'Chat room updated.',
|
|
721
|
+
write: true,
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
export async function runChatRoomArchive(options = {}) {
|
|
726
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
727
|
+
const input = compactPayload({
|
|
728
|
+
id: requireField(options.id, '--id'),
|
|
729
|
+
updatedBy: normalizeNonEmpty(options.updatedBy),
|
|
730
|
+
});
|
|
731
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
732
|
+
command: 'chat.room.archive',
|
|
733
|
+
toolId: 'agentspace.chat-room.archive',
|
|
734
|
+
input,
|
|
735
|
+
successText: 'Chat room archived.',
|
|
736
|
+
write: true,
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
export async function runChatRoomOpenDm(options = {}) {
|
|
741
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
742
|
+
const seed = seedSection(parseJsonSeed(options.input), 'data');
|
|
743
|
+
const agentIds = resolveStringArrayField(options.agent, seed, 'agentIds') ?? [];
|
|
744
|
+
if (agentIds.length !== 2)
|
|
745
|
+
throw new Error('Open DM requires exactly two --agent values or input.agentIds entries.');
|
|
746
|
+
const explicitRoles = parseJsonObjectInput(options.roles, '--roles');
|
|
747
|
+
const roles = explicitRoles ?? parseRoleSpecs(options.role) ?? (isRecord(seed.roles) ? seed.roles : undefined);
|
|
748
|
+
const input = {
|
|
749
|
+
data: compactPayload({
|
|
750
|
+
scopeId: requireScopeId(resolvedContext, seed, 'Chat room open-dm'),
|
|
751
|
+
agentIds,
|
|
752
|
+
projectId: normalizeNonEmpty(options.projectId) ?? normalizeNonEmpty(resolvedContext.projectId) ?? normalizeNonEmpty(seed.projectId),
|
|
753
|
+
title: resolveStringField(options.title, seed, 'title'),
|
|
754
|
+
purpose: resolveStringField(options.purpose, seed, 'purpose'),
|
|
755
|
+
guidanceMarkdown: resolveTextField(options.guidance, seed, 'guidanceMarkdown'),
|
|
756
|
+
roles,
|
|
757
|
+
createdBy: resolveStringField(options.createdBy, seed, 'createdBy'),
|
|
758
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
759
|
+
}),
|
|
760
|
+
};
|
|
761
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
762
|
+
command: 'chat.room.open-dm',
|
|
763
|
+
toolId: 'agentspace.chat-room.open-dm',
|
|
764
|
+
input,
|
|
765
|
+
successText: 'Chat DM opened.',
|
|
766
|
+
write: true,
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
export async function runChatRoomManifest(options = {}) {
|
|
771
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
772
|
+
const input = {
|
|
773
|
+
data: compactPayload({
|
|
774
|
+
roomId: requireField(options.roomId, '--room-id'),
|
|
775
|
+
includeMessages: options.includeMessages === true ? true : undefined,
|
|
776
|
+
}),
|
|
777
|
+
};
|
|
778
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
779
|
+
command: 'chat.room.manifest',
|
|
780
|
+
toolId: 'agentspace.chat-room.export-manifest',
|
|
781
|
+
input,
|
|
782
|
+
successText: options.out ? `Chat room manifest exported to ${options.out}.` : 'Chat room manifest exported.',
|
|
783
|
+
resultMapper: options.summary ? summarizeManifestResult : undefined,
|
|
784
|
+
out: normalizeNonEmpty(options.out),
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
export async function runChatRoomBrief(options = {}) {
|
|
789
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
790
|
+
const input = {
|
|
791
|
+
data: {
|
|
792
|
+
roomId: requireField(options.roomId, '--room-id'),
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
const result = await invokeChatRaw(apiState, options, resolvedContext, {
|
|
796
|
+
toolId: 'agentspace.chat-room.export-manifest',
|
|
797
|
+
input,
|
|
798
|
+
});
|
|
799
|
+
const briefMarkdown = buildRoomBriefMarkdown(result, { forAgent: options.for });
|
|
800
|
+
const output = {
|
|
801
|
+
manifest: summarizeManifestResult(result),
|
|
802
|
+
briefMarkdown,
|
|
803
|
+
};
|
|
804
|
+
if (options.json) {
|
|
805
|
+
console.log(JSON.stringify(buildEnvelope({
|
|
806
|
+
command: 'chat.room.brief',
|
|
807
|
+
toolId: 'agentspace.chat-room.export-manifest',
|
|
808
|
+
surface: 'agentspace-chat-v1',
|
|
809
|
+
resolvedContext: buildResolvedContextRecord(resolvedContext),
|
|
810
|
+
input,
|
|
811
|
+
result: output,
|
|
812
|
+
}), null, 2));
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
console.log(briefMarkdown);
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
export async function runChatMemberAdd(options = {}) {
|
|
819
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
820
|
+
const seed = seedSection(parseJsonSeed(options.input), 'data');
|
|
821
|
+
const input = {
|
|
822
|
+
data: compactPayload({
|
|
823
|
+
scopeId: requireScopeId(resolvedContext, seed, 'Chat member add'),
|
|
824
|
+
roomId: requireField(resolveStringField(options.roomId, seed, 'roomId'), '--room-id'),
|
|
825
|
+
agentId: requireField(resolveStringField(options.agent, seed, 'agentId'), '--agent'),
|
|
826
|
+
roleKey: resolveStringField(options.role, seed, 'roleKey'),
|
|
827
|
+
brief: resolveStringField(options.brief, seed, 'brief'),
|
|
828
|
+
status: resolveStringField(options.status, seed, 'status'),
|
|
829
|
+
lastReadSeq: resolveIntegerField(options.lastReadSeq, seed, 'lastReadSeq', '--last-read-seq'),
|
|
830
|
+
createdBy: resolveStringField(options.createdBy, seed, 'createdBy'),
|
|
831
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
832
|
+
}),
|
|
833
|
+
};
|
|
834
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
835
|
+
command: 'chat.member.add',
|
|
836
|
+
toolId: 'agentspace.chat-member.add',
|
|
837
|
+
input,
|
|
838
|
+
successText: 'Chat member added.',
|
|
839
|
+
write: true,
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
export async function runChatMemberUpdate(options = {}) {
|
|
844
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
845
|
+
const rawSeed = parseJsonSeed(options.input);
|
|
846
|
+
const seed = seedSection(rawSeed, 'patch');
|
|
847
|
+
const patch = compactPayload({
|
|
848
|
+
roleKey: resolveStringField(options.role, seed, 'roleKey'),
|
|
849
|
+
brief: resolveStringField(options.brief, seed, 'brief'),
|
|
850
|
+
status: resolveStringField(options.status, seed, 'status'),
|
|
851
|
+
lastReadSeq: resolveIntegerField(options.lastReadSeq, seed, 'lastReadSeq', '--last-read-seq'),
|
|
852
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
853
|
+
});
|
|
854
|
+
if (Object.keys(patch).length === 0)
|
|
855
|
+
throw new Error('Provide at least one chat member field to update.');
|
|
856
|
+
const input = {
|
|
857
|
+
id: requireField(resolveStringField(options.id, rawSeed, 'id'), '--id'),
|
|
858
|
+
patch,
|
|
859
|
+
};
|
|
860
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
861
|
+
command: 'chat.member.update',
|
|
862
|
+
toolId: 'agentspace.chat-member.update',
|
|
863
|
+
input,
|
|
864
|
+
successText: 'Chat member updated.',
|
|
865
|
+
write: true,
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
export async function runChatMemberRemove(options = {}) {
|
|
870
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
871
|
+
const seed = seedSection(parseJsonSeed(options.input), 'data');
|
|
872
|
+
const memberId = resolveStringField(options.memberId, seed, 'memberId');
|
|
873
|
+
const roomId = resolveStringField(options.roomId, seed, 'roomId');
|
|
874
|
+
const agentId = resolveStringField(options.agent, seed, 'agentId');
|
|
875
|
+
if (!memberId && (!roomId || !agentId)) {
|
|
876
|
+
throw new Error('Chat member remove requires --member-id or both --room-id and --agent.');
|
|
877
|
+
}
|
|
878
|
+
const input = {
|
|
879
|
+
data: compactPayload({
|
|
880
|
+
memberId,
|
|
881
|
+
roomId,
|
|
882
|
+
agentId,
|
|
883
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
884
|
+
}),
|
|
885
|
+
};
|
|
886
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
887
|
+
command: 'chat.member.remove',
|
|
888
|
+
toolId: 'agentspace.chat-member.remove',
|
|
889
|
+
input,
|
|
890
|
+
successText: 'Chat member removed.',
|
|
891
|
+
write: true,
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
export async function runChatBindingAdd(options = {}) {
|
|
896
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
897
|
+
const seed = seedSection(parseJsonSeed(options.input), 'data');
|
|
898
|
+
const input = {
|
|
899
|
+
data: compactPayload({
|
|
900
|
+
scopeId: requireScopeId(resolvedContext, seed, 'Chat binding add'),
|
|
901
|
+
roomId: requireField(resolveStringField(options.roomId, seed, 'roomId'), '--room-id'),
|
|
902
|
+
bindingType: requireField(resolveStringField(options.bindingType, seed, 'bindingType'), '--binding-type'),
|
|
903
|
+
refId: resolveStringField(options.refId, seed, 'refId'),
|
|
904
|
+
uri: resolveStringField(options.uri, seed, 'uri'),
|
|
905
|
+
title: resolveStringField(options.title, seed, 'title'),
|
|
906
|
+
note: resolveStringField(options.note, seed, 'note'),
|
|
907
|
+
createdBy: resolveStringField(options.createdBy, seed, 'createdBy'),
|
|
908
|
+
updatedBy: resolveStringField(options.updatedBy, seed, 'updatedBy'),
|
|
909
|
+
}),
|
|
910
|
+
};
|
|
911
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
912
|
+
command: 'chat.binding.add',
|
|
913
|
+
toolId: 'agentspace.chat-binding.add',
|
|
914
|
+
input,
|
|
915
|
+
successText: 'Chat binding added.',
|
|
916
|
+
write: true,
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
export async function runChatBindingRemove(options = {}) {
|
|
921
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
922
|
+
const input = { id: requireField(options.id, '--id') };
|
|
923
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
924
|
+
command: 'chat.binding.remove',
|
|
925
|
+
toolId: 'agentspace.chat-binding.remove',
|
|
926
|
+
input,
|
|
927
|
+
successText: 'Chat binding removed.',
|
|
928
|
+
write: true,
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
export async function runChatMessageSend(options = {}) {
|
|
933
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
934
|
+
const seed = seedSection(parseJsonSeed(options.input), 'data');
|
|
935
|
+
const input = {
|
|
936
|
+
data: compactPayload({
|
|
937
|
+
scopeId: requireScopeId(resolvedContext, seed, 'Chat message send'),
|
|
938
|
+
roomId: requireField(resolveStringField(options.roomId, seed, 'roomId'), '--room-id'),
|
|
939
|
+
authorAgentId: requireField(resolveStringField(options.from, seed, 'authorAgentId'), '--from'),
|
|
940
|
+
text: requireField(resolveTextField(options.text, seed, 'text'), '--text'),
|
|
941
|
+
mentions: resolveStringArrayField(options.mention, seed, 'mentions'),
|
|
942
|
+
replyToSeq: resolveIntegerField(options.replyToSeq, seed, 'replyToSeq', '--reply-to-seq'),
|
|
943
|
+
idempotencyKey: normalizeNonEmpty(options.idempotencyKey) ?? normalizeNonEmpty(seed.idempotencyKey),
|
|
944
|
+
createdBy: resolveStringField(options.createdBy, seed, 'createdBy'),
|
|
945
|
+
}),
|
|
946
|
+
};
|
|
947
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
948
|
+
command: 'chat.message.send',
|
|
949
|
+
toolId: 'agentspace.chat-message.send',
|
|
950
|
+
input,
|
|
951
|
+
successText: 'Chat message sent.',
|
|
952
|
+
write: true,
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
export async function runChatMessageList(options = {}) {
|
|
957
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
958
|
+
const input = {
|
|
959
|
+
filter: compactPayload({
|
|
960
|
+
roomId: normalizeNonEmpty(options.roomId),
|
|
961
|
+
scopeId: normalizeNonEmpty(resolvedContext.scopeId),
|
|
962
|
+
authorAgentId: normalizeNonEmpty(options.author),
|
|
963
|
+
afterSeq: options.afterSeq !== undefined ? toInteger(options.afterSeq, '--after-seq') : undefined,
|
|
964
|
+
idempotencyKey: normalizeNonEmpty(options.idempotencyKey),
|
|
965
|
+
}),
|
|
966
|
+
options: compactPayload({
|
|
967
|
+
limit: options.limit !== undefined ? toInteger(options.limit, '--limit') : undefined,
|
|
968
|
+
}),
|
|
969
|
+
};
|
|
970
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
971
|
+
command: 'chat.message.list',
|
|
972
|
+
toolId: 'agentspace.chat-message.list',
|
|
973
|
+
input,
|
|
974
|
+
successText: 'Chat messages loaded.',
|
|
975
|
+
resultMapper: options.summary
|
|
976
|
+
? (result) => summarizeArrayResult(result, summarizeMessage, 'chat-message-list-summary', 'Use `aops-cli chat message list --room-id <room-id> --json` without --summary for full message records.')
|
|
977
|
+
: undefined,
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
export async function runChatCatchup(options = {}) {
|
|
982
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
983
|
+
const input = buildCatchupInput(options);
|
|
984
|
+
const agentId = requireField(options.for, '--for');
|
|
985
|
+
const peek = options.peek === true;
|
|
986
|
+
if (!peek && options.apply !== true && options.preview !== true) {
|
|
987
|
+
throw new Error('Chat catchup advances the read cursor by default. Retry with --apply, use --preview, or add --peek for read-only catchup.');
|
|
988
|
+
}
|
|
989
|
+
const rawResult = await invokeChatRaw(apiState, options, resolvedContext, {
|
|
990
|
+
toolId: 'agentspace.chat.catchup',
|
|
991
|
+
input,
|
|
992
|
+
});
|
|
993
|
+
const cursorUpdates = plannedCursorUpdates(rawResult, agentId);
|
|
994
|
+
const cursorResults = !peek && options.apply === true
|
|
995
|
+
? await applyCursorUpdates(apiState, options, resolvedContext, cursorUpdates)
|
|
996
|
+
: [];
|
|
997
|
+
const result = compactPayload({
|
|
998
|
+
catchup: options.summary ? summarizeCatchupResult(rawResult) : rawResult,
|
|
999
|
+
cursor: compactPayload({
|
|
1000
|
+
mode: peek ? 'peek' : options.preview ? 'preview' : 'advance',
|
|
1001
|
+
planned: cursorUpdates,
|
|
1002
|
+
applied: cursorResults.length,
|
|
1003
|
+
}),
|
|
1004
|
+
});
|
|
1005
|
+
writeChatEnvelope({
|
|
1006
|
+
options,
|
|
1007
|
+
command: 'chat.catchup',
|
|
1008
|
+
toolId: 'agentspace.chat.catchup',
|
|
1009
|
+
resolvedContext,
|
|
1010
|
+
input,
|
|
1011
|
+
result,
|
|
1012
|
+
successText: peek ? 'Chat catchup loaded without advancing cursors.' : 'Chat catchup loaded and cursors advanced.',
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
export async function runChatInbox(options = {}) {
|
|
1017
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
1018
|
+
const input = buildCatchupInput(options);
|
|
1019
|
+
const rawResult = await invokeChatRaw(apiState, options, resolvedContext, {
|
|
1020
|
+
toolId: 'agentspace.chat.catchup',
|
|
1021
|
+
input,
|
|
1022
|
+
});
|
|
1023
|
+
writeChatEnvelope({
|
|
1024
|
+
options,
|
|
1025
|
+
command: 'chat.inbox',
|
|
1026
|
+
toolId: 'agentspace.chat.catchup',
|
|
1027
|
+
resolvedContext,
|
|
1028
|
+
input,
|
|
1029
|
+
result: options.summary ? summarizeCatchupResult(rawResult) : rawResult,
|
|
1030
|
+
successText: 'Chat inbox loaded.',
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
export async function runChatListen(options = {}) {
|
|
1035
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
1036
|
+
const input = buildCatchupInput(options);
|
|
1037
|
+
const roomScoped = Boolean(normalizeNonEmpty(options.roomId));
|
|
1038
|
+
const timeoutSec = listenTimeoutMs(options.timeoutSec, 540, '--timeout-sec');
|
|
1039
|
+
const intervalSec = listenTimeoutMs(options.intervalSec, 5, '--interval-sec');
|
|
1040
|
+
const maxLoops = options.maxLoops !== undefined ? toInteger(options.maxLoops, '--max-loops') : undefined;
|
|
1041
|
+
const startedAt = Date.now();
|
|
1042
|
+
const deadline = startedAt + timeoutSec * 1000;
|
|
1043
|
+
let loop = 0;
|
|
1044
|
+
let lastResult;
|
|
1045
|
+
let exitCode = 22;
|
|
1046
|
+
let outcome = 'timeout';
|
|
1047
|
+
while (Date.now() <= deadline) {
|
|
1048
|
+
loop += 1;
|
|
1049
|
+
try {
|
|
1050
|
+
lastResult = await invokeChatRaw(apiState, options, resolvedContext, {
|
|
1051
|
+
toolId: 'agentspace.chat.catchup',
|
|
1052
|
+
input,
|
|
1053
|
+
});
|
|
1054
|
+
if (!roomScoped)
|
|
1055
|
+
lastResult = filterTerminalCatchupRooms(lastResult);
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
if (roomScoped && isTerminalListenError(error)) {
|
|
1059
|
+
outcome = 'terminal';
|
|
1060
|
+
exitCode = 21;
|
|
1061
|
+
lastResult = { error: error instanceof Error ? error.message : String(error) };
|
|
1062
|
+
break;
|
|
1063
|
+
}
|
|
1064
|
+
throw error;
|
|
1065
|
+
}
|
|
1066
|
+
if (roomScoped && catchupHasTerminalRoom(lastResult)) {
|
|
1067
|
+
outcome = 'terminal';
|
|
1068
|
+
exitCode = 21;
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
if (catchupHasUnread(lastResult)) {
|
|
1072
|
+
outcome = 'work-ready';
|
|
1073
|
+
exitCode = 0;
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
if (maxLoops !== undefined && loop >= maxLoops)
|
|
1077
|
+
break;
|
|
1078
|
+
if (Date.now() + intervalSec * 1000 > deadline)
|
|
1079
|
+
break;
|
|
1080
|
+
await delay(intervalSec * 1000);
|
|
1081
|
+
}
|
|
1082
|
+
if (exitCode !== 0)
|
|
1083
|
+
process.exitCode = exitCode;
|
|
1084
|
+
const result = {
|
|
1085
|
+
listen: {
|
|
1086
|
+
agentId: requireField(options.for, '--for'),
|
|
1087
|
+
roomId: normalizeNonEmpty(options.roomId),
|
|
1088
|
+
outcome,
|
|
1089
|
+
exitCode,
|
|
1090
|
+
loop,
|
|
1091
|
+
maxLoops,
|
|
1092
|
+
timeoutSec,
|
|
1093
|
+
intervalSec,
|
|
1094
|
+
cursorAdvanced: false,
|
|
1095
|
+
},
|
|
1096
|
+
catchup: options.summary ? summarizeCatchupResult(lastResult) : lastResult,
|
|
1097
|
+
};
|
|
1098
|
+
writeChatEnvelope({
|
|
1099
|
+
options,
|
|
1100
|
+
command: 'chat.listen',
|
|
1101
|
+
toolId: 'agentspace.chat.catchup',
|
|
1102
|
+
resolvedContext,
|
|
1103
|
+
input,
|
|
1104
|
+
result,
|
|
1105
|
+
successText: exitCode === 0 ? 'Unread chat work is ready.' : exitCode === 21 ? 'Chat room is no longer active for this listener.' : 'Chat listen timed out.',
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
export async function runChatMarkRead(options = {}) {
|
|
1110
|
+
await withChatContext(options, async (apiState, resolvedContext) => {
|
|
1111
|
+
const input = {
|
|
1112
|
+
data: compactPayload({
|
|
1113
|
+
roomId: requireField(options.roomId, '--room-id'),
|
|
1114
|
+
agentId: requireField(options.agent, '--agent'),
|
|
1115
|
+
seq: options.seq !== undefined ? toInteger(options.seq, '--seq') : undefined,
|
|
1116
|
+
updatedBy: normalizeNonEmpty(options.updatedBy),
|
|
1117
|
+
}),
|
|
1118
|
+
};
|
|
1119
|
+
await invokeChatTool(apiState, options, resolvedContext, {
|
|
1120
|
+
command: 'chat.mark-read',
|
|
1121
|
+
toolId: 'agentspace.chat.mark-read',
|
|
1122
|
+
input,
|
|
1123
|
+
successText: 'Chat read cursor updated.',
|
|
1124
|
+
write: true,
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
function applyChatContextOptions(cmd, params = {}) {
|
|
1129
|
+
applyCommonOptions(cmd);
|
|
1130
|
+
cmd.option('--project-id <id>', 'Project id used to resolve repo-bound chat ownership');
|
|
1131
|
+
cmd.option('--project-name <name>', 'Project name used to resolve repo-bound chat ownership');
|
|
1132
|
+
cmd.option('--scope-id <id>', 'Explicit scope id override for chat ownership');
|
|
1133
|
+
if (params.withScopeResolution) {
|
|
1134
|
+
cmd.option('--scope-resolution <mode>', 'Scope resolution policy: explicit or cascade');
|
|
1135
|
+
}
|
|
1136
|
+
return cmd;
|
|
1137
|
+
}
|
|
1138
|
+
function applyWriteGuards(cmd) {
|
|
1139
|
+
cmd.option('--preview', 'Return a validated preflight summary without executing the tool');
|
|
1140
|
+
cmd.option('--apply', 'Execute the hosted write operation');
|
|
1141
|
+
cmd.option('--idempotency-key <key>', 'Optional idempotency key for hosted writes');
|
|
1142
|
+
return cmd;
|
|
1143
|
+
}
|
|
1144
|
+
function applyJsonSeedOption(cmd) {
|
|
1145
|
+
cmd.option('--input <jsonOrFile>', 'Optional command data seed, hosted input envelope, or @file.json');
|
|
1146
|
+
return cmd;
|
|
1147
|
+
}
|
|
1148
|
+
function addSchemaHelp(cmd, toolId) {
|
|
1149
|
+
cmd.addHelpText('after', `
|
|
1150
|
+
Schema:
|
|
1151
|
+
aops-cli agent schema --tool ${toolId} --summary --json
|
|
1152
|
+
`);
|
|
1153
|
+
return cmd;
|
|
1154
|
+
}
|
|
1155
|
+
export function makeChatCommand() {
|
|
1156
|
+
const cmd = new Command('chat').description('Hosted Agentspace chat rooms/DMs for agent-to-agent messaging; separate from the now-retired collab chat and the legacy codex-chat bridge tools');
|
|
1157
|
+
const room = cmd.command('room').description('Chat room lifecycle, DM, and manifest commands');
|
|
1158
|
+
applyChatContextOptions(room.command('list')
|
|
1159
|
+
.description('List chat rooms')
|
|
1160
|
+
.option('--slug <slug>', 'Room slug filter')
|
|
1161
|
+
.option('--title <text>', 'Room title filter')
|
|
1162
|
+
.option('--kind <kind>', 'Room kind filter')
|
|
1163
|
+
.option('--status <status>', 'Room status filter')
|
|
1164
|
+
.option('--limit <n>', 'Optional item limit')
|
|
1165
|
+
.option('--summary', 'Print compact room records'), { withScopeResolution: true }).action(async (options) => {
|
|
1166
|
+
await runChatRoomList(options);
|
|
1167
|
+
});
|
|
1168
|
+
applyChatContextOptions(room.command('get')
|
|
1169
|
+
.description('Get a chat room by id')
|
|
1170
|
+
.requiredOption('--id <id>', 'Room id')).action(async (options) => {
|
|
1171
|
+
await runChatRoomGet(options);
|
|
1172
|
+
});
|
|
1173
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(room.command('create')
|
|
1174
|
+
.description('Create a group chat room')
|
|
1175
|
+
.option('--slug <slug>', 'Room slug')
|
|
1176
|
+
.option('--title <text>', 'Room title')
|
|
1177
|
+
.option('--kind <kind>', 'Room kind; v1 expects group')
|
|
1178
|
+
.option('--purpose <text>', 'Room purpose')
|
|
1179
|
+
.option('--guidance <textOrFile>', 'Room guidance markdown or @file.md')
|
|
1180
|
+
.option('--status <status>', 'Room status')
|
|
1181
|
+
.option('--created-by <agent>', 'Creator agent id; auto-added as roleKey creator when absent from members')
|
|
1182
|
+
.option('--updated-by <agent>', 'Updater agent id')
|
|
1183
|
+
.option('--member <agent[:role]|json>', 'Initial member; repeatable', collectRepeatedOption, [])
|
|
1184
|
+
.option('--binding <key=value,...|json>', 'Initial binding; repeatable. Complex bindings may be passed through --input.', collectRepeatedOption, [])))), 'agentspace.chat-room.create').action(async (options) => {
|
|
1185
|
+
await runChatRoomCreate(options);
|
|
1186
|
+
});
|
|
1187
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(room.command('update')
|
|
1188
|
+
.description('Update mutable chat room fields')
|
|
1189
|
+
.option('--id <id>', 'Room id')
|
|
1190
|
+
.option('--title <text>', 'Room title')
|
|
1191
|
+
.option('--purpose <text>', 'Room purpose')
|
|
1192
|
+
.option('--guidance <textOrFile>', 'Room guidance markdown or @file.md')
|
|
1193
|
+
.option('--updated-by <agent>', 'Updater agent id')))), 'agentspace.chat-room.update').action(async (options) => {
|
|
1194
|
+
await runChatRoomUpdate(options);
|
|
1195
|
+
});
|
|
1196
|
+
addSchemaHelp(applyWriteGuards(applyChatContextOptions(room.command('archive')
|
|
1197
|
+
.description('Archive a chat room')
|
|
1198
|
+
.requiredOption('--id <id>', 'Room id')
|
|
1199
|
+
.option('--updated-by <agent>', 'Updater agent id'))), 'agentspace.chat-room.archive').action(async (options) => {
|
|
1200
|
+
await runChatRoomArchive(options);
|
|
1201
|
+
});
|
|
1202
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(room.command('open-dm')
|
|
1203
|
+
.alias('dm')
|
|
1204
|
+
.description('Open or create a deterministic direct-message room for two agents')
|
|
1205
|
+
.option('--agent <agent>', 'DM participant; repeat exactly twice', collectRepeatedOption, [])
|
|
1206
|
+
.option('--title <text>', 'Room title')
|
|
1207
|
+
.option('--purpose <text>', 'Room purpose')
|
|
1208
|
+
.option('--guidance <textOrFile>', 'Room guidance markdown or @file.md')
|
|
1209
|
+
.option('--role <agent=role>', 'Participant role mapping; repeatable')
|
|
1210
|
+
.option('--roles <jsonOrFile>', 'JSON role mapping object or @file.json')
|
|
1211
|
+
.option('--created-by <agent>', 'Creator agent id')
|
|
1212
|
+
.option('--updated-by <agent>', 'Updater agent id')))), 'agentspace.chat-room.open-dm').action(async (options) => {
|
|
1213
|
+
await runChatRoomOpenDm(options);
|
|
1214
|
+
});
|
|
1215
|
+
applyChatContextOptions(room.command('manifest')
|
|
1216
|
+
.alias('export-manifest')
|
|
1217
|
+
.description('Export a room manifest as JSON')
|
|
1218
|
+
.requiredOption('--room-id <id>', 'Room id')
|
|
1219
|
+
.option('--include-messages', 'Include room messages in the manifest')
|
|
1220
|
+
.option('--out <path>', 'Write manifest result JSON to a file')
|
|
1221
|
+
.option('--summary', 'Print compact manifest records')).action(async (options) => {
|
|
1222
|
+
await runChatRoomManifest(options);
|
|
1223
|
+
});
|
|
1224
|
+
applyChatContextOptions(room.command('brief')
|
|
1225
|
+
.description('Render a paste-ready onboarding brief from the room manifest')
|
|
1226
|
+
.requiredOption('--room-id <id>', 'Room id')
|
|
1227
|
+
.option('--for <agent>', 'Agent id the brief is being prepared for')).action(async (options) => {
|
|
1228
|
+
await runChatRoomBrief(options);
|
|
1229
|
+
});
|
|
1230
|
+
const member = cmd.command('member').description('Chat room member commands');
|
|
1231
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(member.command('add')
|
|
1232
|
+
.description('Add or reactivate a room member')
|
|
1233
|
+
.option('--room-id <id>', 'Room id')
|
|
1234
|
+
.option('--agent <agent>', 'Agent id')
|
|
1235
|
+
.option('--role <role>', 'Member roleKey')
|
|
1236
|
+
.option('--brief <text>', 'Member brief')
|
|
1237
|
+
.option('--status <status>', 'Member status')
|
|
1238
|
+
.option('--last-read-seq <seq>', 'Initial read cursor')
|
|
1239
|
+
.option('--created-by <agent>', 'Creator agent id')
|
|
1240
|
+
.option('--updated-by <agent>', 'Updater agent id')))), 'agentspace.chat-member.add').action(async (options) => {
|
|
1241
|
+
await runChatMemberAdd(options);
|
|
1242
|
+
});
|
|
1243
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(member.command('update')
|
|
1244
|
+
.description('Update room member fields')
|
|
1245
|
+
.option('--id <id>', 'Member id')
|
|
1246
|
+
.option('--role <role>', 'Member roleKey')
|
|
1247
|
+
.option('--brief <text>', 'Member brief')
|
|
1248
|
+
.option('--status <status>', 'Member status')
|
|
1249
|
+
.option('--last-read-seq <seq>', 'Read cursor')
|
|
1250
|
+
.option('--updated-by <agent>', 'Updater agent id')))), 'agentspace.chat-member.update').action(async (options) => {
|
|
1251
|
+
await runChatMemberUpdate(options);
|
|
1252
|
+
});
|
|
1253
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(member.command('remove')
|
|
1254
|
+
.description('Mark a room member as left')
|
|
1255
|
+
.option('--member-id <id>', 'Member id')
|
|
1256
|
+
.option('--room-id <id>', 'Room id; use with --agent when --member-id is omitted')
|
|
1257
|
+
.option('--agent <agent>', 'Agent id; use with --room-id when --member-id is omitted')
|
|
1258
|
+
.option('--updated-by <agent>', 'Updater agent id')))), 'agentspace.chat-member.remove').action(async (options) => {
|
|
1259
|
+
await runChatMemberRemove(options);
|
|
1260
|
+
});
|
|
1261
|
+
const binding = cmd.command('binding').description('Chat room reference binding commands');
|
|
1262
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(binding.command('add')
|
|
1263
|
+
.description('Add a reference binding to a room')
|
|
1264
|
+
.option('--room-id <id>', 'Room id')
|
|
1265
|
+
.option('--binding-type <type>', 'Binding type, such as projectman.kanban-task, repo.url, docman.document, skill, or agents.md')
|
|
1266
|
+
.option('--ref-id <id>', 'Bound reference id')
|
|
1267
|
+
.option('--uri <uri>', 'Bound reference URI')
|
|
1268
|
+
.option('--title <text>', 'Binding title')
|
|
1269
|
+
.option('--note <text>', 'Binding note')
|
|
1270
|
+
.option('--created-by <agent>', 'Creator agent id')
|
|
1271
|
+
.option('--updated-by <agent>', 'Updater agent id')))), 'agentspace.chat-binding.add').action(async (options) => {
|
|
1272
|
+
await runChatBindingAdd(options);
|
|
1273
|
+
});
|
|
1274
|
+
addSchemaHelp(applyWriteGuards(applyChatContextOptions(binding.command('remove')
|
|
1275
|
+
.description('Remove a reference binding from a room')
|
|
1276
|
+
.requiredOption('--id <id>', 'Binding id'))), 'agentspace.chat-binding.remove').action(async (options) => {
|
|
1277
|
+
await runChatBindingRemove(options);
|
|
1278
|
+
});
|
|
1279
|
+
const message = cmd.command('message').description('Chat message commands');
|
|
1280
|
+
addSchemaHelp(applyWriteGuards(applyJsonSeedOption(applyChatContextOptions(message.command('send')
|
|
1281
|
+
.description('Send an append-only message to a room')
|
|
1282
|
+
.option('--room-id <id>', 'Room id')
|
|
1283
|
+
.option('--from <agent>', 'Author agent id')
|
|
1284
|
+
.option('--text <textOrFile>', 'Message text or @file.md')
|
|
1285
|
+
.option('--mention <agent>', 'Mentioned agent id; repeatable', collectRepeatedOption, [])
|
|
1286
|
+
.option('--reply-to-seq <seq>', 'Reply target message sequence')
|
|
1287
|
+
.option('--created-by <agent>', 'Creator agent id')))), 'agentspace.chat-message.send').action(async (options) => {
|
|
1288
|
+
await runChatMessageSend(options);
|
|
1289
|
+
});
|
|
1290
|
+
applyChatContextOptions(message.command('list')
|
|
1291
|
+
.description('List room messages')
|
|
1292
|
+
.option('--room-id <id>', 'Room id filter')
|
|
1293
|
+
.option('--author <agent>', 'Author agent id filter')
|
|
1294
|
+
.option('--after-seq <seq>', 'Only return messages after this sequence')
|
|
1295
|
+
.option('--idempotency-key <key>', 'Message idempotency key filter')
|
|
1296
|
+
.option('--limit <n>', 'Optional item limit')
|
|
1297
|
+
.option('--summary', 'Print compact message records')).action(async (options) => {
|
|
1298
|
+
await runChatMessageList(options);
|
|
1299
|
+
});
|
|
1300
|
+
applyChatContextOptions(cmd.command('catchup')
|
|
1301
|
+
.description('Read unread messages and advance the read cursor by default')
|
|
1302
|
+
.requiredOption('--for <agent>', 'Agent id')
|
|
1303
|
+
.option('--room-id <id>', 'Optional room id')
|
|
1304
|
+
.option('--limit <n>', 'Optional per-room message limit')
|
|
1305
|
+
.option('--peek', 'Read without advancing the cursor')
|
|
1306
|
+
.option('--summary', 'Print compact catchup records'))
|
|
1307
|
+
.option('--preview', 'Plan cursor advancement without writing read cursors')
|
|
1308
|
+
.option('--apply', 'Advance read cursors after reading unread messages')
|
|
1309
|
+
.option('--idempotency-key <key>', 'Optional idempotency key prefix for cursor updates')
|
|
1310
|
+
.action(async (options) => {
|
|
1311
|
+
await runChatCatchup(options);
|
|
1312
|
+
});
|
|
1313
|
+
applyChatContextOptions(cmd.command('inbox')
|
|
1314
|
+
.description('Peek unread messages without advancing read cursors')
|
|
1315
|
+
.requiredOption('--for <agent>', 'Agent id')
|
|
1316
|
+
.option('--room-id <id>', 'Optional room id')
|
|
1317
|
+
.option('--limit <n>', 'Optional per-room message limit')
|
|
1318
|
+
.option('--summary', 'Print compact inbox records')).action(async (options) => {
|
|
1319
|
+
await runChatInbox(options);
|
|
1320
|
+
});
|
|
1321
|
+
applyChatContextOptions(cmd.command('listen')
|
|
1322
|
+
.description('Poll for unread chat without advancing read cursors; exits 0 unread, 21 room-scoped terminal, 22 timeout')
|
|
1323
|
+
.requiredOption('--for <agent>', 'Agent id')
|
|
1324
|
+
.option('--room-id <id>', 'Optional room id')
|
|
1325
|
+
.option('--limit <n>', 'Optional per-room message limit')
|
|
1326
|
+
.option('--timeout-sec <sec>', 'Maximum wait seconds', '540')
|
|
1327
|
+
.option('--interval-sec <sec>', 'Polling interval seconds', '5')
|
|
1328
|
+
.option('--max-loops <n>', 'Optional maximum poll loops')
|
|
1329
|
+
.option('--summary', 'Print compact catchup records')).action(async (options) => {
|
|
1330
|
+
await runChatListen(options);
|
|
1331
|
+
});
|
|
1332
|
+
addSchemaHelp(applyWriteGuards(applyChatContextOptions(cmd.command('mark-read')
|
|
1333
|
+
.description('Advance an agent read cursor in a room')
|
|
1334
|
+
.requiredOption('--room-id <id>', 'Room id')
|
|
1335
|
+
.requiredOption('--agent <agent>', 'Agent id')
|
|
1336
|
+
.option('--seq <seq>', 'Read cursor sequence; omit to mark through current room lastSeq')
|
|
1337
|
+
.option('--updated-by <agent>', 'Updater agent id'))), 'agentspace.chat.mark-read').action(async (options) => {
|
|
1338
|
+
await runChatMarkRead(options);
|
|
1339
|
+
});
|
|
1340
|
+
cmd.addHelpText('after', buildOperatorCookbook({
|
|
1341
|
+
examples: [
|
|
1342
|
+
'aops-cli chat room create --slug design-room --title "Design Room" --created-by codex --apply --json',
|
|
1343
|
+
'aops-cli chat room open-dm --agent codex --agent claude --created-by codex --apply --json',
|
|
1344
|
+
'aops-cli chat message send --room-id <room-id> --from codex --text "Ready for review." --apply --json',
|
|
1345
|
+
'aops-cli chat inbox --for claude --summary --json',
|
|
1346
|
+
'aops-cli chat listen --for claude --timeout-sec 60 --interval-sec 5 --json',
|
|
1347
|
+
'aops-cli chat catchup --for claude --apply --summary --json',
|
|
1348
|
+
'aops-cli chat mark-read --room-id <room-id> --agent claude --seq <seq> --apply --json',
|
|
1349
|
+
'aops-cli chat room brief --room-id <room-id> --for claude',
|
|
1350
|
+
'aops-cli chat room manifest --room-id <room-id> --include-messages --out ./chat-manifest.json --json',
|
|
1351
|
+
],
|
|
1352
|
+
guide: GUIDE_PATHS.agentspace,
|
|
1353
|
+
notes: [
|
|
1354
|
+
'Disambiguation: `aops-cli chat` wraps the new hosted `agentspace.chat-*` room/DM tools. It is not the now-retired `aops-cli collab chat`, and it is not the legacy `agentspace.codex-chat-*` Codex SDK app bridge.',
|
|
1355
|
+
'`chat inbox` and `chat listen` never advance read cursors. `chat catchup` advances by default through `agentspace.chat.mark-read`; add --peek for a read-only catchup.',
|
|
1356
|
+
'Agent-wide `chat listen` ignores archived/left rooms; exit 21 is reserved for --room-id listen when that room is archived or membership ended.',
|
|
1357
|
+
'Listener exit codes: 0 unread found, 21 room-scoped terminal, 22 timeout.',
|
|
1358
|
+
'Writes are thin hosted-tool invokes; run `aops-cli agent schema --tool agentspace.chat-message.send --summary --json` before authoring raw --input payloads.',
|
|
1359
|
+
'Use `--summary` on list/catchup/manifest reads for compact agent-facing output.',
|
|
1360
|
+
],
|
|
1361
|
+
}));
|
|
1362
|
+
return cmd;
|
|
1363
|
+
}
|