@lotics/cli 0.91.2 → 0.93.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/README.md +25 -1
- package/dist/src/cli.js +381 -20
- package/dist/src/client.d.ts +60 -0
- package/dist/src/client.js +19 -0
- package/docs/knowledge_docs.md +39 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ package (reachable at `node_modules/@lotics/cli/docs/*.md` once installed):
|
|
|
20
20
|
create → generate → chain lifecycle, and the marker capabilities.
|
|
21
21
|
- [`docs/knowledge_docs.md`](docs/knowledge_docs.md) — the AI's rulebook layer: authoring the
|
|
22
22
|
workspace facts an agent can't guess, the access-vs-activation model, and the
|
|
23
|
-
|
|
23
|
+
catalog-then-stage retrieval model agents use to pull only the lines they need.
|
|
24
24
|
|
|
25
25
|
Both point to `lotics tools <name>` for exact input schemas.
|
|
26
26
|
|
|
@@ -164,6 +164,22 @@ Edits mutate the file in place via an atomic temp-file + rename. Unknown OOXML c
|
|
|
164
164
|
|
|
165
165
|
Run `lotics xlsx` or `lotics docx` with no subcommand for the full list.
|
|
166
166
|
|
|
167
|
+
## Knowledge docs
|
|
168
|
+
|
|
169
|
+
A file-native surface over the workspace's knowledge docs — the AI's rulebook layer. The body is a Markdown file: `create`/`update` read it from your filesystem, `get` writes it back. See [`docs/knowledge_docs.md`](docs/knowledge_docs.md) for the model.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
lotics knowledge list # catalog: id, name, description (--json)
|
|
173
|
+
lotics knowledge create --name "Shipping tariffs" \
|
|
174
|
+
--description "HS-coded rates; searchable by lane and code" \
|
|
175
|
+
--from ./tariffs.md # or --content '<inline>'; prints the new id
|
|
176
|
+
lotics knowledge get kdc_... -o ./tariffs.md # body → file (omit -o for stdout; --json = full doc)
|
|
177
|
+
lotics knowledge update kdc_... --from ./tariffs.md # send only what changed (--name / --description too)
|
|
178
|
+
lotics knowledge rm kdc_... # archive
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`create` / `update` send the body inline; the server mints and version-chains the content file (and `update` diffs + resolves concurrency internally — no token to pass). `get` is the one content-read path, hydrating the body server-side.
|
|
182
|
+
|
|
167
183
|
## Custom-code apps
|
|
168
184
|
|
|
169
185
|
```bash
|
|
@@ -201,6 +217,14 @@ lotics app workflow set issueInvoice # push the edited src/workflows/issu
|
|
|
201
217
|
# authoritative, so the next `app deploy` re-syncs it — keep the manifest current.
|
|
202
218
|
lotics app query set openInvoices # push package.json#lotics.queries.openInvoices
|
|
203
219
|
|
|
220
|
+
# Run a bound app agent end-to-end (no deployed UI needed — app row + declaration
|
|
221
|
+
# + member auth). Streams progress to stderr; reports the SETTLED run (structured
|
|
222
|
+
# output / final text) to stdout; exits 0 only when the run completed.
|
|
223
|
+
lotics app agent run app_abc recognize '{"image_file_id":"fil_..."}'
|
|
224
|
+
cat input.json | lotics app agent run app_abc recognize # inputs via stdin/@file
|
|
225
|
+
lotics app agent run app_abc recognize --json # full run summary to stdout
|
|
226
|
+
lotics app agent run app_abc recognize --session cli-123 '{}' # continue an existing thread
|
|
227
|
+
|
|
204
228
|
# Dev-link @lotics/ui to packages/ui/src for live HMR (Vite alias; deploy bundles it)
|
|
205
229
|
lotics ui link card # monorepo: packages/ui/src found automatically
|
|
206
230
|
lotics ui link card --ui-src /abs/monorepo/packages/ui/src # external app (e.g. ~/lotics_apps)
|
package/dist/src/cli.js
CHANGED
|
@@ -22135,7 +22135,7 @@ var require_lib2 = __commonJS({
|
|
|
22135
22135
|
}
|
|
22136
22136
|
if (this.state !== PENDING) {
|
|
22137
22137
|
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
|
|
22138
|
-
|
|
22138
|
+
unwrap2(promise2, resolver, this.outcome);
|
|
22139
22139
|
} else {
|
|
22140
22140
|
this.queue.push(new QueueItem(promise2, onFulfilled, onRejected));
|
|
22141
22141
|
}
|
|
@@ -22156,15 +22156,15 @@ var require_lib2 = __commonJS({
|
|
|
22156
22156
|
handlers.resolve(this.promise, value);
|
|
22157
22157
|
};
|
|
22158
22158
|
QueueItem.prototype.otherCallFulfilled = function(value) {
|
|
22159
|
-
|
|
22159
|
+
unwrap2(this.promise, this.onFulfilled, value);
|
|
22160
22160
|
};
|
|
22161
22161
|
QueueItem.prototype.callRejected = function(value) {
|
|
22162
22162
|
handlers.reject(this.promise, value);
|
|
22163
22163
|
};
|
|
22164
22164
|
QueueItem.prototype.otherCallRejected = function(value) {
|
|
22165
|
-
|
|
22165
|
+
unwrap2(this.promise, this.onRejected, value);
|
|
22166
22166
|
};
|
|
22167
|
-
function
|
|
22167
|
+
function unwrap2(promise2, func, value) {
|
|
22168
22168
|
immediate(function() {
|
|
22169
22169
|
var returnValue;
|
|
22170
22170
|
try {
|
|
@@ -29585,7 +29585,7 @@ var require_lib3 = __commonJS({
|
|
|
29585
29585
|
// src/cli.ts
|
|
29586
29586
|
import dns from "node:dns";
|
|
29587
29587
|
import net2 from "node:net";
|
|
29588
|
-
import
|
|
29588
|
+
import fs9 from "node:fs";
|
|
29589
29589
|
import path7 from "node:path";
|
|
29590
29590
|
import readline from "node:readline";
|
|
29591
29591
|
|
|
@@ -29750,6 +29750,16 @@ var LoticsClient = class {
|
|
|
29750
29750
|
}
|
|
29751
29751
|
return this.request("POST", "/v1/tools/execute", body);
|
|
29752
29752
|
}
|
|
29753
|
+
// --- Knowledge docs ---
|
|
29754
|
+
/**
|
|
29755
|
+
* Fetch one knowledge doc with its hydrated `content` — the single content-read
|
|
29756
|
+
* path for a non-sandbox client (the `list_knowledge` tool returns metadata
|
|
29757
|
+
* only, and the sandbox-staging read path is unavailable here). Works for both
|
|
29758
|
+
* the file-model and legacy parked-column rows. Mirrors GET /v1/knowledge_docs/{id}.
|
|
29759
|
+
*/
|
|
29760
|
+
async getKnowledgeDoc(knowledge_doc_id) {
|
|
29761
|
+
return this.request("GET", `/v1/knowledge_docs/${encodeURIComponent(knowledge_doc_id)}`);
|
|
29762
|
+
}
|
|
29753
29763
|
// --- Apps ---
|
|
29754
29764
|
async getApp(app_id) {
|
|
29755
29765
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}`);
|
|
@@ -30214,6 +30224,18 @@ var LoticsClient = class {
|
|
|
30214
30224
|
if (!res.ok) await this.throwResponseError(res);
|
|
30215
30225
|
return res;
|
|
30216
30226
|
}
|
|
30227
|
+
/**
|
|
30228
|
+
* A session's app-agent run history, oldest-first (the run just started is the
|
|
30229
|
+
* last, and its exact id is on the stream response's `x-app-agent-run-id`
|
|
30230
|
+
* header). Transcript excluded; structured `output`/`input` included. Mirrors
|
|
30231
|
+
* GET /v1/apps/{app_id}/agent-runs.
|
|
30232
|
+
*/
|
|
30233
|
+
async listAgentRuns(app_id, session_id) {
|
|
30234
|
+
return this.request(
|
|
30235
|
+
"GET",
|
|
30236
|
+
`/v1/apps/${encodeURIComponent(app_id)}/agent-runs?session_id=${encodeURIComponent(session_id)}`
|
|
30237
|
+
);
|
|
30238
|
+
}
|
|
30217
30239
|
/**
|
|
30218
30240
|
* Mint a presigned URL for uploading a file into an app. Mirrors
|
|
30219
30241
|
* POST /v1/apps/{app_id}/files/upload-url.
|
|
@@ -30545,6 +30567,16 @@ function upsertProfile(orgId, fields) {
|
|
|
30545
30567
|
latest_version: config2.latest_version
|
|
30546
30568
|
});
|
|
30547
30569
|
}
|
|
30570
|
+
function clearProfileWorkspace(orgId) {
|
|
30571
|
+
const config2 = loadGlobalConfig();
|
|
30572
|
+
const existing = config2?.profiles?.[orgId];
|
|
30573
|
+
if (!existing) return;
|
|
30574
|
+
const { workspace_id: _dropped, ...rest } = existing;
|
|
30575
|
+
saveGlobalConfig({
|
|
30576
|
+
...config2,
|
|
30577
|
+
profiles: { ...config2.profiles, [orgId]: { ...rest } }
|
|
30578
|
+
});
|
|
30579
|
+
}
|
|
30548
30580
|
function removeProfile(orgId) {
|
|
30549
30581
|
const config2 = loadGlobalConfig();
|
|
30550
30582
|
if (!config2?.profiles?.[orgId]) return;
|
|
@@ -30643,6 +30675,7 @@ var VERSION = pkg.version;
|
|
|
30643
30675
|
import fs4 from "node:fs";
|
|
30644
30676
|
import path5 from "node:path";
|
|
30645
30677
|
import { spawn as spawn2 } from "node:child_process";
|
|
30678
|
+
import { randomUUID } from "node:crypto";
|
|
30646
30679
|
import { tmpdir } from "node:os";
|
|
30647
30680
|
|
|
30648
30681
|
// src/starter_template.ts
|
|
@@ -33650,6 +33683,96 @@ async function appExecuteWorkflow(client, args) {
|
|
|
33650
33683
|
}
|
|
33651
33684
|
if (status === "error" || cleanupFailed) process.exit(1);
|
|
33652
33685
|
}
|
|
33686
|
+
async function streamAgentTextDeltas(body, onText) {
|
|
33687
|
+
const reader = body.getReader();
|
|
33688
|
+
const decoder = new TextDecoder();
|
|
33689
|
+
let buffer = "";
|
|
33690
|
+
let accumulated = "";
|
|
33691
|
+
try {
|
|
33692
|
+
for (; ; ) {
|
|
33693
|
+
const { value, done } = await reader.read();
|
|
33694
|
+
if (done) break;
|
|
33695
|
+
buffer += decoder.decode(value, { stream: true });
|
|
33696
|
+
const frames = buffer.split("\n\n");
|
|
33697
|
+
buffer = frames.pop() ?? "";
|
|
33698
|
+
for (const frame of frames) {
|
|
33699
|
+
for (const line of frame.split("\n")) {
|
|
33700
|
+
if (!line.startsWith("data:")) continue;
|
|
33701
|
+
const payload = line.slice(5).trim();
|
|
33702
|
+
if (!payload || payload === "[DONE]") continue;
|
|
33703
|
+
let chunk;
|
|
33704
|
+
try {
|
|
33705
|
+
chunk = JSON.parse(payload);
|
|
33706
|
+
} catch {
|
|
33707
|
+
continue;
|
|
33708
|
+
}
|
|
33709
|
+
if (chunk.type === "text-delta" && chunk.delta) {
|
|
33710
|
+
accumulated += chunk.delta;
|
|
33711
|
+
onText(chunk.delta);
|
|
33712
|
+
}
|
|
33713
|
+
}
|
|
33714
|
+
}
|
|
33715
|
+
}
|
|
33716
|
+
} catch {
|
|
33717
|
+
} finally {
|
|
33718
|
+
reader.releaseLock();
|
|
33719
|
+
}
|
|
33720
|
+
return accumulated;
|
|
33721
|
+
}
|
|
33722
|
+
var AGENT_RUN_POLL = {
|
|
33723
|
+
intervalMs: 1e3,
|
|
33724
|
+
settleTimeoutMs: 21 * 60 * 1e3,
|
|
33725
|
+
existenceTimeoutMs: 5e3
|
|
33726
|
+
};
|
|
33727
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
33728
|
+
async function fetchSettledAgentRun(client, appId, sessionId, runId, timing) {
|
|
33729
|
+
const settleDeadline = Date.now() + timing.settleTimeoutMs;
|
|
33730
|
+
const existenceDeadline = Date.now() + timing.existenceTimeoutMs;
|
|
33731
|
+
for (; ; ) {
|
|
33732
|
+
const { runs } = await client.listAgentRuns(appId, sessionId);
|
|
33733
|
+
const target = runId ? runs.find((r) => r.id === runId) : runs[runs.length - 1];
|
|
33734
|
+
if (target) {
|
|
33735
|
+
if (target.status !== "running" || Date.now() >= settleDeadline) return target;
|
|
33736
|
+
} else if (Date.now() >= existenceDeadline) {
|
|
33737
|
+
return void 0;
|
|
33738
|
+
}
|
|
33739
|
+
await sleep(timing.intervalMs);
|
|
33740
|
+
}
|
|
33741
|
+
}
|
|
33742
|
+
async function appAgentRun(client, args, timing = AGENT_RUN_POLL) {
|
|
33743
|
+
const sessionId = args.sessionId ?? `cli-${randomUUID()}`;
|
|
33744
|
+
const continuing = args.sessionId !== void 0;
|
|
33745
|
+
const res = await client.appAgentRunStream(args.app_id, args.alias, {
|
|
33746
|
+
session_id: sessionId,
|
|
33747
|
+
input: args.input
|
|
33748
|
+
});
|
|
33749
|
+
const runId = res.headers.get("x-app-agent-run-id") ?? void 0;
|
|
33750
|
+
if (res.body) {
|
|
33751
|
+
await streamAgentTextDeltas(res.body, (piece) => process.stderr.write(piece));
|
|
33752
|
+
}
|
|
33753
|
+
const run = await fetchSettledAgentRun(client, args.app_id, sessionId, runId, timing);
|
|
33754
|
+
if (!run) {
|
|
33755
|
+
console.error(`
|
|
33756
|
+
Could not find the settled run for session ${sessionId} on ${args.app_id}.`);
|
|
33757
|
+
console.error("The run may still be in progress \u2014 re-check with `lotics run` against the app's agent-runs.");
|
|
33758
|
+
process.exit(1);
|
|
33759
|
+
}
|
|
33760
|
+
if (args.json) {
|
|
33761
|
+
console.log(JSON.stringify(run, null, 2));
|
|
33762
|
+
} else if (run.output !== null && typeof run.output === "object") {
|
|
33763
|
+
console.log(JSON.stringify(run.output, null, 2));
|
|
33764
|
+
} else if (typeof run.output === "string") {
|
|
33765
|
+
console.log(run.output);
|
|
33766
|
+
}
|
|
33767
|
+
console.error(
|
|
33768
|
+
`
|
|
33769
|
+
Agent "${args.alias}" run ${run.id} \u2192 ${run.status}${run.error_message ? `: ${run.error_message}` : ""}`
|
|
33770
|
+
);
|
|
33771
|
+
console.error(
|
|
33772
|
+
continuing ? `Session: ${sessionId}` : `Session: ${sessionId} (fresh \u2014 pass --session ${sessionId} to continue this thread)`
|
|
33773
|
+
);
|
|
33774
|
+
if (run.status !== "completed") process.exit(1);
|
|
33775
|
+
}
|
|
33653
33776
|
async function appWorkflowSet(client, args) {
|
|
33654
33777
|
const projectDir = process.cwd();
|
|
33655
33778
|
const meta3 = readAppMeta(projectDir);
|
|
@@ -33876,8 +33999,12 @@ function parseArgs(argv) {
|
|
|
33876
33999
|
viewAs: void 0,
|
|
33877
34000
|
uiSrc: void 0,
|
|
33878
34001
|
name: void 0,
|
|
34002
|
+
description: void 0,
|
|
34003
|
+
from: void 0,
|
|
34004
|
+
content: void 0,
|
|
33879
34005
|
timezone: void 0,
|
|
33880
34006
|
message: void 0,
|
|
34007
|
+
session: void 0,
|
|
33881
34008
|
local: false,
|
|
33882
34009
|
all: false,
|
|
33883
34010
|
yes: false,
|
|
@@ -33923,6 +34050,15 @@ function parseArgs(argv) {
|
|
|
33923
34050
|
case "--name":
|
|
33924
34051
|
flags.name = argv[++i2];
|
|
33925
34052
|
break;
|
|
34053
|
+
case "--description":
|
|
34054
|
+
flags.description = argv[++i2];
|
|
34055
|
+
break;
|
|
34056
|
+
case "--from":
|
|
34057
|
+
flags.from = argv[++i2];
|
|
34058
|
+
break;
|
|
34059
|
+
case "--content":
|
|
34060
|
+
flags.content = argv[++i2];
|
|
34061
|
+
break;
|
|
33926
34062
|
case "--timezone":
|
|
33927
34063
|
flags.timezone = argv[++i2];
|
|
33928
34064
|
break;
|
|
@@ -33930,6 +34066,9 @@ function parseArgs(argv) {
|
|
|
33930
34066
|
case "--message":
|
|
33931
34067
|
flags.message = argv[++i2];
|
|
33932
34068
|
break;
|
|
34069
|
+
case "--session":
|
|
34070
|
+
flags.session = argv[++i2];
|
|
34071
|
+
break;
|
|
33933
34072
|
case "--local":
|
|
33934
34073
|
flags.local = true;
|
|
33935
34074
|
break;
|
|
@@ -34000,6 +34139,36 @@ async function ingestJsonArgs(opts) {
|
|
|
34000
34139
|
}
|
|
34001
34140
|
}
|
|
34002
34141
|
|
|
34142
|
+
// src/org_commands.ts
|
|
34143
|
+
function printWorkspaceList(workspaces, currentId) {
|
|
34144
|
+
for (const ws of workspaces) {
|
|
34145
|
+
const marker = ws.id === currentId ? " (current)" : "";
|
|
34146
|
+
console.error(` ${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
|
|
34147
|
+
}
|
|
34148
|
+
}
|
|
34149
|
+
async function validateOrgWorkspacePin(client, orgId, profile) {
|
|
34150
|
+
const pinned = profile.workspace_id;
|
|
34151
|
+
if (!pinned) return;
|
|
34152
|
+
let workspaces;
|
|
34153
|
+
try {
|
|
34154
|
+
workspaces = await client.listWorkspaces();
|
|
34155
|
+
} catch (error51) {
|
|
34156
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
34157
|
+
console.error(`Could not validate the pinned workspace (${message}) \u2014 keeping ${pinned}.`);
|
|
34158
|
+
return;
|
|
34159
|
+
}
|
|
34160
|
+
if (workspaces.some((w) => w.id === pinned)) return;
|
|
34161
|
+
clearProfileWorkspace(orgId);
|
|
34162
|
+
console.error(`Pinned workspace ${pinned} is no longer in ${profile.org_name} \u2014 cleared the stale pin.`);
|
|
34163
|
+
if (workspaces.length === 0) {
|
|
34164
|
+
console.error("This organization has no workspaces yet.");
|
|
34165
|
+
return;
|
|
34166
|
+
}
|
|
34167
|
+
console.error("Select one with:\n");
|
|
34168
|
+
console.error(" lotics workspace select <id>\n");
|
|
34169
|
+
printWorkspaceList(workspaces);
|
|
34170
|
+
}
|
|
34171
|
+
|
|
34003
34172
|
// src/xlsx.ts
|
|
34004
34173
|
import fs6 from "node:fs";
|
|
34005
34174
|
|
|
@@ -67828,6 +67997,148 @@ async function runDocxCommand(subcommand, toolArgs, restArgs) {
|
|
|
67828
67997
|
}
|
|
67829
67998
|
}
|
|
67830
67999
|
|
|
68000
|
+
// src/knowledge.ts
|
|
68001
|
+
import fs8 from "node:fs";
|
|
68002
|
+
function readBodyFile(filePath) {
|
|
68003
|
+
if (!fs8.existsSync(filePath)) fail(`File not found: ${filePath}`);
|
|
68004
|
+
return fs8.readFileSync(filePath, "utf-8");
|
|
68005
|
+
}
|
|
68006
|
+
function resolveBody(flags, required2) {
|
|
68007
|
+
if (flags.from !== void 0 && flags.content !== void 0) {
|
|
68008
|
+
fail("Pass either --from <file> or --content <str>, not both.");
|
|
68009
|
+
}
|
|
68010
|
+
if (flags.from !== void 0) return readBodyFile(flags.from);
|
|
68011
|
+
if (flags.content !== void 0) return flags.content;
|
|
68012
|
+
if (required2) fail("A body is required \u2014 pass --from <file.md> or --content <str>.");
|
|
68013
|
+
return void 0;
|
|
68014
|
+
}
|
|
68015
|
+
function unwrap(res) {
|
|
68016
|
+
if (res.error) fail(res.error);
|
|
68017
|
+
return res.result;
|
|
68018
|
+
}
|
|
68019
|
+
async function knowledgeList(client, flags) {
|
|
68020
|
+
const result = unwrap(await client.execute("list_knowledge", {}));
|
|
68021
|
+
const payload = result;
|
|
68022
|
+
const results = Array.isArray(payload?.results) ? payload.results : [];
|
|
68023
|
+
if (flags.json) {
|
|
68024
|
+
console.log(JSON.stringify(results, null, 2));
|
|
68025
|
+
return;
|
|
68026
|
+
}
|
|
68027
|
+
if (results.length === 0) {
|
|
68028
|
+
console.error("No knowledge docs in this workspace.");
|
|
68029
|
+
return;
|
|
68030
|
+
}
|
|
68031
|
+
const rows = results.map((r) => {
|
|
68032
|
+
const doc = r;
|
|
68033
|
+
return {
|
|
68034
|
+
id: typeof doc.id === "string" ? doc.id : "",
|
|
68035
|
+
name: typeof doc.name === "string" ? doc.name : "",
|
|
68036
|
+
// Collapse whitespace so a multi-line description never breaks the row.
|
|
68037
|
+
description: typeof doc.description === "string" ? doc.description.replace(/\s+/g, " ").trim() : ""
|
|
68038
|
+
};
|
|
68039
|
+
});
|
|
68040
|
+
const wId = Math.max("ID".length, ...rows.map((r) => r.id.length));
|
|
68041
|
+
const wName = Math.max("NAME".length, ...rows.map((r) => r.name.length));
|
|
68042
|
+
const pad = (s, n) => s.padEnd(n);
|
|
68043
|
+
console.log(`${pad("ID", wId)} ${pad("NAME", wName)} DESCRIPTION`);
|
|
68044
|
+
for (const r of rows) {
|
|
68045
|
+
console.log(`${pad(r.id, wId)} ${pad(r.name, wName)} ${r.description}`.trimEnd());
|
|
68046
|
+
}
|
|
68047
|
+
}
|
|
68048
|
+
async function knowledgeCreate(client, flags) {
|
|
68049
|
+
const name = flags.name;
|
|
68050
|
+
if (!name) fail("Usage: lotics knowledge create --name <name> [--description <d>] (--from <file.md> | --content <str>)");
|
|
68051
|
+
const content = resolveBody(flags, true);
|
|
68052
|
+
const result = unwrap(
|
|
68053
|
+
await client.execute("create_knowledge", {
|
|
68054
|
+
name,
|
|
68055
|
+
// The tool requires a description (empty string is accepted); the CLI
|
|
68056
|
+
// treats it as optional and defaults to "".
|
|
68057
|
+
description: flags.description ?? "",
|
|
68058
|
+
content
|
|
68059
|
+
})
|
|
68060
|
+
);
|
|
68061
|
+
if (flags.json) {
|
|
68062
|
+
console.log(JSON.stringify(result, null, 2));
|
|
68063
|
+
return;
|
|
68064
|
+
}
|
|
68065
|
+
const doc = result;
|
|
68066
|
+
const id = typeof doc.id === "string" ? doc.id : "";
|
|
68067
|
+
console.error(`Created knowledge doc: ${typeof doc.name === "string" ? doc.name : name} (${id})`);
|
|
68068
|
+
console.log(id);
|
|
68069
|
+
}
|
|
68070
|
+
async function knowledgeGet(client, id, flags) {
|
|
68071
|
+
if (!id) fail("Usage: lotics knowledge get <id> [-o <file.md>]");
|
|
68072
|
+
const doc = await client.getKnowledgeDoc(id);
|
|
68073
|
+
if (flags.output) {
|
|
68074
|
+
writeFileAtomic(flags.output, Buffer.from(doc.content, "utf-8"));
|
|
68075
|
+
return;
|
|
68076
|
+
}
|
|
68077
|
+
if (flags.json) {
|
|
68078
|
+
console.log(JSON.stringify(doc, null, 2));
|
|
68079
|
+
return;
|
|
68080
|
+
}
|
|
68081
|
+
console.log(doc.content);
|
|
68082
|
+
}
|
|
68083
|
+
async function knowledgeUpdate(client, id, flags) {
|
|
68084
|
+
if (!id) fail("Usage: lotics knowledge update <id> [--from <file.md> | --content <str>] [--name <n>] [--description <d>]");
|
|
68085
|
+
const content = resolveBody(flags, false);
|
|
68086
|
+
if (content === void 0 && flags.name === void 0 && flags.description === void 0) {
|
|
68087
|
+
fail("Nothing to update. Pass at least one of --from/--content, --name, or --description.");
|
|
68088
|
+
}
|
|
68089
|
+
const args = { knowledge_doc_id: id };
|
|
68090
|
+
if (content !== void 0) args.content = content;
|
|
68091
|
+
if (flags.name !== void 0) args.name = flags.name;
|
|
68092
|
+
if (flags.description !== void 0) args.description = flags.description;
|
|
68093
|
+
const result = unwrap(await client.execute("update_knowledge", args));
|
|
68094
|
+
if (flags.json) {
|
|
68095
|
+
console.log(JSON.stringify(result, null, 2));
|
|
68096
|
+
return;
|
|
68097
|
+
}
|
|
68098
|
+
console.error(`Updated knowledge doc: ${id}`);
|
|
68099
|
+
}
|
|
68100
|
+
async function knowledgeRm(client, id, flags) {
|
|
68101
|
+
if (!id) fail("Usage: lotics knowledge rm <id>");
|
|
68102
|
+
const result = unwrap(await client.execute("delete_knowledge", { knowledge_doc_id: id }));
|
|
68103
|
+
if (flags.json) {
|
|
68104
|
+
console.log(JSON.stringify(result, null, 2));
|
|
68105
|
+
return;
|
|
68106
|
+
}
|
|
68107
|
+
console.error(`Deleted knowledge doc: ${id}`);
|
|
68108
|
+
}
|
|
68109
|
+
function printKnowledgeHelp() {
|
|
68110
|
+
console.error(`Lotics knowledge commands \u2014 manage the AI's rulebook docs.
|
|
68111
|
+
|
|
68112
|
+
lotics knowledge list [--json]
|
|
68113
|
+
Catalog every doc you can use (id, name, description).
|
|
68114
|
+
lotics knowledge create --name <n> [--description <d>] (--from <file.md> | --content <str>) [--json]
|
|
68115
|
+
Create a doc; the body comes from a file or an inline string. Prints the new id.
|
|
68116
|
+
lotics knowledge get <id> [-o <file.md>] [--json]
|
|
68117
|
+
Fetch a doc's content \u2014 to <file.md>, or stdout. --json prints the full doc.
|
|
68118
|
+
lotics knowledge update <id> [--from <file.md> | --content <str>] [--name <n>] [--description <d>] [--json]
|
|
68119
|
+
Update a doc; sends only the fields you pass.
|
|
68120
|
+
lotics knowledge rm <id> [--json]
|
|
68121
|
+
Archive a doc.`);
|
|
68122
|
+
}
|
|
68123
|
+
async function runKnowledgeCommand(client, subcommand, toolArgs, flags) {
|
|
68124
|
+
switch (subcommand) {
|
|
68125
|
+
case "list":
|
|
68126
|
+
return knowledgeList(client, flags);
|
|
68127
|
+
case "create":
|
|
68128
|
+
return knowledgeCreate(client, flags);
|
|
68129
|
+
case "get":
|
|
68130
|
+
return knowledgeGet(client, toolArgs, flags);
|
|
68131
|
+
case "update":
|
|
68132
|
+
return knowledgeUpdate(client, toolArgs, flags);
|
|
68133
|
+
case "rm":
|
|
68134
|
+
return knowledgeRm(client, toolArgs, flags);
|
|
68135
|
+
default:
|
|
68136
|
+
if (subcommand) fail(`Unknown knowledge subcommand: ${subcommand}`);
|
|
68137
|
+
printKnowledgeHelp();
|
|
68138
|
+
return;
|
|
68139
|
+
}
|
|
68140
|
+
}
|
|
68141
|
+
|
|
67831
68142
|
// src/preview.ts
|
|
67832
68143
|
import { spawn as spawn3 } from "node:child_process";
|
|
67833
68144
|
import { createServer } from "node:http";
|
|
@@ -67835,7 +68146,7 @@ import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdtempSync,
|
|
|
67835
68146
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
67836
68147
|
import { join, dirname, resolve, extname, basename } from "node:path";
|
|
67837
68148
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
67838
|
-
import { setTimeout as
|
|
68149
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
67839
68150
|
var HERE = dirname(fileURLToPath2(import.meta.url));
|
|
67840
68151
|
function fail2(msg) {
|
|
67841
68152
|
console.error(msg);
|
|
@@ -67961,7 +68272,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
67961
68272
|
const p = parseInt(readFileSync2(portFile, "utf8").split("\n")[0], 10);
|
|
67962
68273
|
if (p) cdpPort = p;
|
|
67963
68274
|
}
|
|
67964
|
-
if (!cdpPort) await
|
|
68275
|
+
if (!cdpPort) await sleep2(100);
|
|
67965
68276
|
}
|
|
67966
68277
|
if (!cdpPort) throw new Error("Chrome did not expose a debugging port (launch failed?).");
|
|
67967
68278
|
let target;
|
|
@@ -67971,7 +68282,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
67971
68282
|
target = list.find((t) => t.type === "page");
|
|
67972
68283
|
} catch {
|
|
67973
68284
|
}
|
|
67974
|
-
if (!target?.webSocketDebuggerUrl) await
|
|
68285
|
+
if (!target?.webSocketDebuggerUrl) await sleep2(100);
|
|
67975
68286
|
}
|
|
67976
68287
|
if (!target?.webSocketDebuggerUrl) throw new Error("No Chrome page target available.");
|
|
67977
68288
|
const cdp = await cdpConnect(target.webSocketDebuggerUrl);
|
|
@@ -67993,7 +68304,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
67993
68304
|
if (v.warnings?.length) warnings.push(...v.warnings);
|
|
67994
68305
|
break;
|
|
67995
68306
|
}
|
|
67996
|
-
await
|
|
68307
|
+
await sleep2(75);
|
|
67997
68308
|
}
|
|
67998
68309
|
if (!done) throw new Error("Render timed out (page never signaled completion).");
|
|
67999
68310
|
if (err2) throw new Error(`Render engine error: ${err2}`);
|
|
@@ -68096,9 +68407,23 @@ COMMANDS
|
|
|
68096
68407
|
lotics app query set <alias> Push package.json#lotics.queries.<alias> to
|
|
68097
68408
|
apps.queries via set_app_query (no deploy;
|
|
68098
68409
|
re-synced by the next deploy from the manifest)
|
|
68410
|
+
lotics app agent run <app_id> <alias> '<json>' Run a bound app agent end-to-end
|
|
68411
|
+
(inputs: inline JSON, @file, or stdin; streams
|
|
68412
|
+
progress to stderr, reports the settled run;
|
|
68413
|
+
--session <id> continues a thread; --json)
|
|
68099
68414
|
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
68100
68415
|
lotics app rename "<new name>" Rename the app's display name (launcher title)
|
|
68101
68416
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
68417
|
+
lotics knowledge list List knowledge docs (id, name, description)
|
|
68418
|
+
lotics knowledge create --name <n> [--description <d>] --from <file.md>
|
|
68419
|
+
Create a doc from a file (or --content <str>);
|
|
68420
|
+
prints the new id
|
|
68421
|
+
lotics knowledge get <id> [-o <file.md>]
|
|
68422
|
+
Fetch a doc's content (to a file, or stdout;
|
|
68423
|
+
--json for the full doc)
|
|
68424
|
+
lotics knowledge update <id> [--from <file.md> | --content <str>] [--name <n>] [--description <d>]
|
|
68425
|
+
Update a doc \u2014 sends only the fields you pass
|
|
68426
|
+
lotics knowledge rm <id> Archive a doc
|
|
68102
68427
|
lotics ui link <component> [--ui-src <path>] [--remove]
|
|
68103
68428
|
Dev-link @lotics/ui to packages/ui/src (Vite alias
|
|
68104
68429
|
+ tsc paths) for live HMR + typecheck. Monorepo apps
|
|
@@ -68321,12 +68646,6 @@ var SOURCE_LABELS = {
|
|
|
68321
68646
|
local_pointer: "local .lotics/config.json (pin)",
|
|
68322
68647
|
global_profile: "global active profile"
|
|
68323
68648
|
};
|
|
68324
|
-
function printWorkspaceList(workspaces, currentId) {
|
|
68325
|
-
for (const ws of workspaces) {
|
|
68326
|
-
const marker = ws.id === currentId ? " (current)" : "";
|
|
68327
|
-
console.error(` ${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
|
|
68328
|
-
}
|
|
68329
|
-
}
|
|
68330
68649
|
async function resolveWorkspace(client, ctx) {
|
|
68331
68650
|
if (ctx.workspaceId) {
|
|
68332
68651
|
client.setWorkspaceId(ctx.workspaceId);
|
|
@@ -68359,9 +68678,9 @@ function resolveUploadPaths(rawPaths) {
|
|
|
68359
68678
|
const result = [];
|
|
68360
68679
|
for (const p of rawPaths) {
|
|
68361
68680
|
const resolved = path7.resolve(p);
|
|
68362
|
-
const stat =
|
|
68681
|
+
const stat = fs9.statSync(resolved);
|
|
68363
68682
|
if (stat.isDirectory()) {
|
|
68364
|
-
const entries =
|
|
68683
|
+
const entries = fs9.readdirSync(resolved, { withFileTypes: true });
|
|
68365
68684
|
for (const entry of entries) {
|
|
68366
68685
|
if (entry.isFile()) {
|
|
68367
68686
|
result.push(path7.join(resolved, entry.name));
|
|
@@ -68541,6 +68860,7 @@ async function main() {
|
|
|
68541
68860
|
console.error("Note: a local pin (.lotics/config.json) overrides the global default in this directory. Use --local to change the pin here.");
|
|
68542
68861
|
}
|
|
68543
68862
|
}
|
|
68863
|
+
await validateOrgWorkspacePin(new LoticsClient({ apiKey: profile.api_key }), orgId, profile);
|
|
68544
68864
|
return;
|
|
68545
68865
|
}
|
|
68546
68866
|
if (subcommand && subcommand !== "list") {
|
|
@@ -68573,7 +68893,7 @@ async function main() {
|
|
|
68573
68893
|
}
|
|
68574
68894
|
return;
|
|
68575
68895
|
}
|
|
68576
|
-
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app") {
|
|
68896
|
+
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "knowledge") {
|
|
68577
68897
|
console.error(`Unknown command: ${command}`);
|
|
68578
68898
|
console.error('Run "lotics --help" for usage.');
|
|
68579
68899
|
process.exit(1);
|
|
@@ -68590,6 +68910,7 @@ async function main() {
|
|
|
68590
68910
|
console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
|
|
68591
68911
|
console.error(" lotics app workflow check [alias] Typecheck src/workflows bodies locally");
|
|
68592
68912
|
console.error(" lotics app query set <alias> Push lotics.queries.<alias> to apps.queries (no deploy)");
|
|
68913
|
+
console.error(" lotics app agent run <app_id> <alias> '<json>' Run a bound app agent (streams progress, reports the settled run)");
|
|
68593
68914
|
console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
|
|
68594
68915
|
console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
|
|
68595
68916
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
@@ -68734,6 +69055,10 @@ Available workspaces:`);
|
|
|
68734
69055
|
return;
|
|
68735
69056
|
}
|
|
68736
69057
|
await resolveWorkspace(client, ctx);
|
|
69058
|
+
if (command === "knowledge") {
|
|
69059
|
+
await runKnowledgeCommand(client, subcommand, toolArgs, flags);
|
|
69060
|
+
return;
|
|
69061
|
+
}
|
|
68737
69062
|
if (command === "app") {
|
|
68738
69063
|
if (subcommand === "create") {
|
|
68739
69064
|
const name = toolArgs;
|
|
@@ -68813,7 +69138,7 @@ Available workspaces:`);
|
|
|
68813
69138
|
const ingested = await ingestJsonArgs({
|
|
68814
69139
|
rawArg: restArgs[1],
|
|
68815
69140
|
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
68816
|
-
readFile: (p) =>
|
|
69141
|
+
readFile: (p) => fs9.readFileSync(p, "utf-8"),
|
|
68817
69142
|
readStdin
|
|
68818
69143
|
});
|
|
68819
69144
|
if (ingested.kind === "error") {
|
|
@@ -68844,6 +69169,42 @@ Available workspaces:`);
|
|
|
68844
69169
|
}
|
|
68845
69170
|
workflowUsage();
|
|
68846
69171
|
}
|
|
69172
|
+
if (subcommand === "agent") {
|
|
69173
|
+
const action = toolArgs;
|
|
69174
|
+
const agentUsage = () => {
|
|
69175
|
+
console.error("Usage: lotics app agent run <app_id> <alias> ['<json>'|@inputs.json|stdin] [--session <id>] [--json]");
|
|
69176
|
+
console.error(" cat inputs.json | lotics app agent run <app_id> <alias> (read inputs from stdin)");
|
|
69177
|
+
console.error("Streams the run's progress to stderr; reports the settled run (structured output / text) to stdout.");
|
|
69178
|
+
console.error("--session <id> continues an existing thread; omitted mints a fresh session per run.");
|
|
69179
|
+
process.exit(1);
|
|
69180
|
+
};
|
|
69181
|
+
if (action === "run") {
|
|
69182
|
+
const appId = restArgs[0];
|
|
69183
|
+
const alias = restArgs[1];
|
|
69184
|
+
if (!appId || !alias) {
|
|
69185
|
+
agentUsage();
|
|
69186
|
+
}
|
|
69187
|
+
const ingested = await ingestJsonArgs({
|
|
69188
|
+
rawArg: restArgs[2],
|
|
69189
|
+
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
69190
|
+
readFile: (p) => fs9.readFileSync(p, "utf-8"),
|
|
69191
|
+
readStdin
|
|
69192
|
+
});
|
|
69193
|
+
if (ingested.kind === "error") {
|
|
69194
|
+
console.error(ingested.message);
|
|
69195
|
+
process.exit(1);
|
|
69196
|
+
}
|
|
69197
|
+
await appAgentRun(client, {
|
|
69198
|
+
app_id: appId,
|
|
69199
|
+
alias,
|
|
69200
|
+
input: ingested.args,
|
|
69201
|
+
sessionId: flags.session,
|
|
69202
|
+
json: flags.json
|
|
69203
|
+
});
|
|
69204
|
+
return;
|
|
69205
|
+
}
|
|
69206
|
+
agentUsage();
|
|
69207
|
+
}
|
|
68847
69208
|
if (subcommand === "query") {
|
|
68848
69209
|
const action = toolArgs;
|
|
68849
69210
|
if (action === "set") {
|
|
@@ -68965,7 +69326,7 @@ ${JSON.stringify(info.input_schema, null, 2)}`);
|
|
|
68965
69326
|
const ingested = await ingestJsonArgs({
|
|
68966
69327
|
rawArg: toolArgs,
|
|
68967
69328
|
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
68968
|
-
readFile: (p) =>
|
|
69329
|
+
readFile: (p) => fs9.readFileSync(p, "utf-8"),
|
|
68969
69330
|
readStdin
|
|
68970
69331
|
});
|
|
68971
69332
|
if (ingested.kind === "error") {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -50,11 +50,55 @@ export interface ToolExecuteResult {
|
|
|
50
50
|
model_output?: string;
|
|
51
51
|
error?: string;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A settled (or in-flight) app-agent run, the transcript-excluded projection
|
|
55
|
+
* `GET /v1/apps/{app_id}/agent-runs` returns. `output` is the STRUCTURED result
|
|
56
|
+
* for a typed agent (an object) or the final text for a free-text agent (a
|
|
57
|
+
* string); `status` is `running` until the run settles to `completed` / `error`
|
|
58
|
+
* / `aborted`. The authoritative record `lotics app agent run` reports from
|
|
59
|
+
* (never the stream).
|
|
60
|
+
*/
|
|
61
|
+
export interface AppAgentRunSummary {
|
|
62
|
+
id: string;
|
|
63
|
+
app_id: string;
|
|
64
|
+
agent_alias: string;
|
|
65
|
+
session_id: string;
|
|
66
|
+
status: string;
|
|
67
|
+
input: Record<string, unknown> | null;
|
|
68
|
+
output: string | Record<string, unknown> | null;
|
|
69
|
+
usage: {
|
|
70
|
+
input_tokens: number;
|
|
71
|
+
output_tokens: number;
|
|
72
|
+
} | null;
|
|
73
|
+
error_message: string | null;
|
|
74
|
+
triggered_by_member_id: string | null;
|
|
75
|
+
started_at: string;
|
|
76
|
+
completed_at: string | null;
|
|
77
|
+
}
|
|
53
78
|
export interface ToolInfo {
|
|
54
79
|
name: string;
|
|
55
80
|
description: string;
|
|
56
81
|
input_schema: unknown;
|
|
57
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* A single knowledge doc with its HYDRATED body — the shape of
|
|
85
|
+
* `GET /v1/knowledge_docs/{id}`. `content` is resolved server-side from the
|
|
86
|
+
* doc's content file (or the parked column for a legacy row), so this is the
|
|
87
|
+
* one content-read path a non-sandbox client has. `content_file_id` is the
|
|
88
|
+
* concurrency token the REST PATCH echoes; the `update_knowledge` TOOL CASes
|
|
89
|
+
* internally, so a CLI caller never needs to pass it.
|
|
90
|
+
*/
|
|
91
|
+
export interface KnowledgeDocDetail {
|
|
92
|
+
id: string;
|
|
93
|
+
workspace_id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
description: string;
|
|
96
|
+
content: string;
|
|
97
|
+
content_file_id: string | null;
|
|
98
|
+
files: unknown[];
|
|
99
|
+
created_at: string;
|
|
100
|
+
updated_at: string;
|
|
101
|
+
}
|
|
58
102
|
export interface FileUploadResult {
|
|
59
103
|
files: Array<{
|
|
60
104
|
id: string;
|
|
@@ -142,6 +186,13 @@ export declare class LoticsClient {
|
|
|
142
186
|
format?: "json" | "text";
|
|
143
187
|
timeoutMs?: number;
|
|
144
188
|
}): Promise<ToolExecuteResult>;
|
|
189
|
+
/**
|
|
190
|
+
* Fetch one knowledge doc with its hydrated `content` — the single content-read
|
|
191
|
+
* path for a non-sandbox client (the `list_knowledge` tool returns metadata
|
|
192
|
+
* only, and the sandbox-staging read path is unavailable here). Works for both
|
|
193
|
+
* the file-model and legacy parked-column rows. Mirrors GET /v1/knowledge_docs/{id}.
|
|
194
|
+
*/
|
|
195
|
+
getKnowledgeDoc(knowledge_doc_id: string): Promise<KnowledgeDocDetail>;
|
|
145
196
|
getApp(app_id: string): Promise<{
|
|
146
197
|
id: string;
|
|
147
198
|
name: string;
|
|
@@ -848,6 +899,15 @@ export declare class LoticsClient {
|
|
|
848
899
|
session_id: string;
|
|
849
900
|
input: Record<string, unknown>;
|
|
850
901
|
}, signal?: AbortSignal): Promise<Response>;
|
|
902
|
+
/**
|
|
903
|
+
* A session's app-agent run history, oldest-first (the run just started is the
|
|
904
|
+
* last, and its exact id is on the stream response's `x-app-agent-run-id`
|
|
905
|
+
* header). Transcript excluded; structured `output`/`input` included. Mirrors
|
|
906
|
+
* GET /v1/apps/{app_id}/agent-runs.
|
|
907
|
+
*/
|
|
908
|
+
listAgentRuns(app_id: string, session_id: string): Promise<{
|
|
909
|
+
runs: AppAgentRunSummary[];
|
|
910
|
+
}>;
|
|
851
911
|
/**
|
|
852
912
|
* Mint a presigned URL for uploading a file into an app. Mirrors
|
|
853
913
|
* POST /v1/apps/{app_id}/files/upload-url.
|
package/dist/src/client.js
CHANGED
|
@@ -187,6 +187,16 @@ export class LoticsClient {
|
|
|
187
187
|
}
|
|
188
188
|
return this.request("POST", "/v1/tools/execute", body);
|
|
189
189
|
}
|
|
190
|
+
// --- Knowledge docs ---
|
|
191
|
+
/**
|
|
192
|
+
* Fetch one knowledge doc with its hydrated `content` — the single content-read
|
|
193
|
+
* path for a non-sandbox client (the `list_knowledge` tool returns metadata
|
|
194
|
+
* only, and the sandbox-staging read path is unavailable here). Works for both
|
|
195
|
+
* the file-model and legacy parked-column rows. Mirrors GET /v1/knowledge_docs/{id}.
|
|
196
|
+
*/
|
|
197
|
+
async getKnowledgeDoc(knowledge_doc_id) {
|
|
198
|
+
return this.request("GET", `/v1/knowledge_docs/${encodeURIComponent(knowledge_doc_id)}`);
|
|
199
|
+
}
|
|
190
200
|
// --- Apps ---
|
|
191
201
|
async getApp(app_id) {
|
|
192
202
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}`);
|
|
@@ -619,6 +629,15 @@ export class LoticsClient {
|
|
|
619
629
|
await this.throwResponseError(res);
|
|
620
630
|
return res;
|
|
621
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* A session's app-agent run history, oldest-first (the run just started is the
|
|
634
|
+
* last, and its exact id is on the stream response's `x-app-agent-run-id`
|
|
635
|
+
* header). Transcript excluded; structured `output`/`input` included. Mirrors
|
|
636
|
+
* GET /v1/apps/{app_id}/agent-runs.
|
|
637
|
+
*/
|
|
638
|
+
async listAgentRuns(app_id, session_id) {
|
|
639
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/agent-runs?session_id=${encodeURIComponent(session_id)}`);
|
|
640
|
+
}
|
|
622
641
|
/**
|
|
623
642
|
* Mint a presigned URL for uploading a file into an app. Mirrors
|
|
624
643
|
* POST /v1/apps/{app_id}/files/upload-url.
|
package/docs/knowledge_docs.md
CHANGED
|
@@ -21,13 +21,17 @@ names, exceptions, and policies. Do **not** put in it general skills the model a
|
|
|
21
21
|
the doc, the doc is noise. A knowledge doc earns its place only by carrying external facts
|
|
22
22
|
tied to your workspace.
|
|
23
23
|
|
|
24
|
-
## Creating a doc — `
|
|
24
|
+
## Creating a doc — `lotics knowledge create`
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
lotics
|
|
27
|
+
lotics knowledge create --name "Shipping tariffs" \
|
|
28
|
+
--description "HS-coded rates; searchable by lane and code" \
|
|
29
|
+
--from ./tariffs.md # the body is a Markdown file; or --content '<inline>'
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
`create_knowledge`
|
|
32
|
+
This reads the body from your filesystem and creates the doc through `create_knowledge`
|
|
33
|
+
(prints the new id). A raw `lotics run create_knowledge @doc.json` works too — read a large
|
|
34
|
+
`content` from a file or stdin. Either way, `create_knowledge` takes three things:
|
|
31
35
|
|
|
32
36
|
- `name` — what it is.
|
|
33
37
|
- `description` — what the doc is **and how to retrieve from it**: its vocabulary, synonyms for
|
|
@@ -57,40 +61,42 @@ So: shared + active → the agent can find and read it. Shared but deactivated
|
|
|
57
61
|
that member's agent. This is the token economy in action — activation is how a member curates
|
|
58
62
|
which rulebooks their agent carries.
|
|
59
63
|
|
|
60
|
-
## How an agent uses a doc —
|
|
64
|
+
## How an agent uses a doc — catalog, then read
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
access + activation, so
|
|
66
|
+
Docs are not injected wholesale. The agent finds the right doc from a **catalog**, then reads
|
|
67
|
+
only what it needs. Both steps respect access + activation, so only docs the caller may use ever
|
|
68
|
+
surface.
|
|
64
69
|
|
|
65
|
-
1. **
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
1-based first line) + `limit` (line count). A large doc never returns whole; reads are
|
|
73
|
-
line-numbered and paged.
|
|
70
|
+
1. **Catalog** — `list_knowledge` returns every usable doc as `{ id, name, description }` — no
|
|
71
|
+
bodies. This is why the *description* carries the weight: it is what the agent reads before
|
|
72
|
+
deciding to open a doc. Write it to sell the doc — its vocabulary, the colloquial synonyms an
|
|
73
|
+
ambiguous query would use, and what it covers.
|
|
74
|
+
2. **Read** — the chat agent stages the chosen doc's content file into a code run and greps it
|
|
75
|
+
there (the body arrives as a file to `cat`/`grep`). Over this CLI you read a body directly
|
|
76
|
+
with `lotics knowledge get <id>`.
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
question without ever loading the rest. Structure your content so it works:
|
|
78
|
+
Structure your content so a reader lands on the answer without loading the rest:
|
|
77
79
|
|
|
78
|
-
- Organize under clear Markdown headers
|
|
80
|
+
- Organize under clear Markdown headers.
|
|
79
81
|
- Keep each searchable unit self-contained — a section for prose, **one record per line** for
|
|
80
82
|
dense/tabular data (a price row, a code entry) — carrying both the terms someone would search
|
|
81
83
|
for and its answer.
|
|
82
84
|
- Lead with the most-queried fields.
|
|
83
85
|
- Note colloquial synonyms next to official terms, so an ambiguous query still matches.
|
|
84
86
|
|
|
85
|
-
## Updating a doc — `
|
|
87
|
+
## Updating a doc — `lotics knowledge update`
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
```bash
|
|
90
|
+
lotics knowledge update kdc_... --from ./tariffs.md # replace the body (--name / --description too)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Send only the fields you're changing. `--from` / `--content` replaces the body; `--name` /
|
|
94
|
+
`--description` change metadata. `update_knowledge` resolves concurrency **internally** — it
|
|
95
|
+
re-reads the current content pointer and version-chains the new body — so there is no version
|
|
96
|
+
token to pass from the CLI. (The chat agent may instead send an `edits` array — anchored
|
|
97
|
+
replace / insert / append — for a surgical change; see `lotics tools update_knowledge`.) Refine
|
|
98
|
+
structure as you learn what users actually ask: add the synonym that failed to match, split the
|
|
99
|
+
section that was too coarse to read.
|
|
94
100
|
|
|
95
101
|
## Package-managed knowledge
|
|
96
102
|
|
|
@@ -103,12 +109,12 @@ same either way; the package layer just manages distribution and version pinning
|
|
|
103
109
|
## Reaching the tools
|
|
104
110
|
|
|
105
111
|
```bash
|
|
106
|
-
lotics
|
|
107
|
-
lotics
|
|
108
|
-
lotics
|
|
109
|
-
echo '{"query":"tariff"}' | lotics run search_knowledge
|
|
112
|
+
lotics knowledge list # catalog: id, name, description
|
|
113
|
+
lotics knowledge get kdc_... -o doc.md # read a body to a file (omit -o for stdout)
|
|
114
|
+
lotics tools update_knowledge # full input schema for any knowledge tool
|
|
110
115
|
```
|
|
111
116
|
|
|
112
|
-
The Knowledge category covers `create_knowledge`, `update_knowledge`,
|
|
113
|
-
`
|
|
114
|
-
to other members is `share_resource` / `unshare_resource` (category
|
|
117
|
+
The Knowledge category covers `list_knowledge`, `create_knowledge`, `update_knowledge`, and
|
|
118
|
+
`delete_knowledge` — fronted by the `lotics knowledge list | create | get | update | rm`
|
|
119
|
+
commands. Sharing a doc to other members is `share_resource` / `unshare_resource` (category
|
|
120
|
+
**Admin**).
|