nolo-cli 0.1.38 → 0.1.40
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 +4 -4
- package/agent-runtime/agentRecordConfig.ts +0 -14
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -1
- package/agent-runtime/hybridRecordStore.ts +1 -1
- package/agent-runtime/index.ts +39 -18
- package/agent-runtime/localDialogRead.ts +23 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +1 -17
- package/agent-runtime/localWorkspaceTools.ts +51 -110
- package/agent-runtime/noloWorkspaceTools.ts +371 -0
- package/agent-runtime/runtimeToolPolicy.ts +3 -14
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +2 -1
- package/agentAliases.ts +8 -1
- package/agentRunCommand.ts +65 -95
- package/agentRuntimeLocal.ts +1 -7
- package/ai/agent/cliExecutor.ts +249 -34
- package/chat/messages/fetchMessages.ts +84 -0
- package/chat/messages/types.ts +185 -0
- package/cli/agentAliases.ts +8 -1
- package/cli/agentRunCommand.ts +65 -95
- package/cli/agentRuntimeLocal.ts +1 -7
- package/cli/cliEnvHelpers.ts +11 -7
- package/cli/client/agentRun.ts +94 -93
- package/cli/client/localRuntimeAdapter.ts +250 -69
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorRunArtifact.ts +0 -1
- package/cli/dialogCommands.ts +572 -0
- package/cli/dialogInternalCommandEntries.ts +4 -0
- package/cli/machineWsRunDispatch.ts +76 -39
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/cli/scriptCommandEntries.ts +0 -2
- package/cli/tui/readlineWorkspace.ts +69 -0
- package/cli/tui/session.ts +176 -1
- package/cliEnvHelpers.ts +11 -7
- package/client/agentConfigResolver.test.ts +4 -5
- package/client/agentRun.test.ts +307 -57
- package/client/agentRun.ts +94 -93
- package/client/localRuntimeAdapter.test.ts +332 -53
- package/client/localRuntimeAdapter.ts +250 -69
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/connectorRunArtifact.ts +0 -1
- package/database/server/db.ts +99 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/levelAuthorityStore.ts +20 -1
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/dialogCommands.ts +572 -0
- package/dialogInternalCommandEntries.ts +4 -0
- package/machineWsRunDispatch.ts +76 -39
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +11 -9
- package/scriptCommandEntries.ts +0 -2
- package/tui/readlineWorkspace.ts +69 -0
- package/tui/session.ts +176 -1
- package/agent-runtime/taskWorkspace.ts +0 -193
- package/agent-runtime/workspaceSession.ts +0 -76
- package/cli/client/taskWorktree.ts +0 -8
- package/cli/client/workspaceSession.ts +0 -11
- package/client/taskWorktree.ts +0 -8
- package/client/workspaceSession.test.ts +0 -57
- package/client/workspaceSession.ts +0 -11
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
lstat,
|
|
3
|
-
mkdir,
|
|
4
|
-
readdir,
|
|
5
|
-
readFile,
|
|
6
|
-
readlink,
|
|
7
|
-
rm,
|
|
8
|
-
symlink,
|
|
9
|
-
unlink,
|
|
10
|
-
} from "node:fs/promises";
|
|
11
|
-
import { dirname, join, resolve, sep } from "node:path";
|
|
12
|
-
|
|
13
|
-
type EnvLike = Record<string, string | undefined>;
|
|
14
|
-
|
|
15
|
-
export type PreparedGitTaskWorkspace = {
|
|
16
|
-
path: string;
|
|
17
|
-
branchName?: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type WorkspacePackageLink = {
|
|
21
|
-
name: string;
|
|
22
|
-
target: string;
|
|
23
|
-
linkPath: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function safeTaskWorkspacePathSegment(input: string) {
|
|
27
|
-
return input.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "agent";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function createTaskWorkspaceId() {
|
|
31
|
-
return Date.now().toString(36).toUpperCase();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function runGitForTaskWorkspace(args: string[], cwd: string) {
|
|
35
|
-
const proc = Bun.spawn(["git", ...args], {
|
|
36
|
-
cwd,
|
|
37
|
-
stdout: "pipe",
|
|
38
|
-
stderr: "pipe",
|
|
39
|
-
stdin: "ignore",
|
|
40
|
-
});
|
|
41
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
42
|
-
new Response(proc.stdout).text(),
|
|
43
|
-
new Response(proc.stderr).text(),
|
|
44
|
-
proc.exited,
|
|
45
|
-
]);
|
|
46
|
-
if (exitCode !== 0) {
|
|
47
|
-
throw new Error(stderr.trim() || stdout.trim() || `git ${args.join(" ")} failed`);
|
|
48
|
-
}
|
|
49
|
-
return stdout.trim();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function packageLinkPath(nodeModulesPath: string, packageName: string) {
|
|
53
|
-
if (!packageName.startsWith("@")) {
|
|
54
|
-
return join(nodeModulesPath, packageName);
|
|
55
|
-
}
|
|
56
|
-
const [scope, name] = packageName.split("/");
|
|
57
|
-
if (!scope || !name) {
|
|
58
|
-
throw new Error(`Invalid scoped package name: ${packageName}`);
|
|
59
|
-
}
|
|
60
|
-
return join(nodeModulesPath, scope, name);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function readPackageName(packageJsonPath: string) {
|
|
64
|
-
const text = await readFile(packageJsonPath, "utf8");
|
|
65
|
-
const parsed = JSON.parse(text) as { name?: unknown };
|
|
66
|
-
return typeof parsed.name === "string" && parsed.name.trim()
|
|
67
|
-
? parsed.name.trim()
|
|
68
|
-
: null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function collectWorkspacePackageLinks(workspacePath: string) {
|
|
72
|
-
const packagesRoot = join(workspacePath, "packages");
|
|
73
|
-
const packageDirs = await readdir(packagesRoot, { withFileTypes: true }).catch(
|
|
74
|
-
() => []
|
|
75
|
-
);
|
|
76
|
-
const nodeModulesPath = join(workspacePath, "node_modules");
|
|
77
|
-
|
|
78
|
-
const linksPromises = packageDirs
|
|
79
|
-
.filter((entry) => entry.isDirectory())
|
|
80
|
-
.map(async (entry) => {
|
|
81
|
-
const target = join(packagesRoot, entry.name);
|
|
82
|
-
const name = await readPackageName(join(target, "package.json")).catch(
|
|
83
|
-
() => null
|
|
84
|
-
);
|
|
85
|
-
if (!name) return null;
|
|
86
|
-
return {
|
|
87
|
-
name,
|
|
88
|
-
target,
|
|
89
|
-
linkPath: packageLinkPath(nodeModulesPath, name),
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const links = (await Promise.all(linksPromises)).filter(Boolean) as WorkspacePackageLink[];
|
|
94
|
-
return links;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function ensureWorkspacePackageLink(link: WorkspacePackageLink) {
|
|
98
|
-
await mkdir(dirname(link.linkPath), { recursive: true });
|
|
99
|
-
const existing = await Bun.file(link.linkPath).stat().catch(() => null);
|
|
100
|
-
if (existing) {
|
|
101
|
-
const existingTarget = await readlinkIfSymlink(link.linkPath);
|
|
102
|
-
if (
|
|
103
|
-
existingTarget &&
|
|
104
|
-
resolve(dirname(link.linkPath), existingTarget) === resolve(link.target)
|
|
105
|
-
) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
if (!existingTarget) {
|
|
109
|
-
throw new Error(`Cannot replace non-symlink workspace package path: ${link.linkPath}`);
|
|
110
|
-
}
|
|
111
|
-
await unlink(link.linkPath);
|
|
112
|
-
}
|
|
113
|
-
await symlink(link.target, link.linkPath, "dir");
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function readlinkIfSymlink(path: string) {
|
|
118
|
-
const stat = await lstat(path).catch(() => null);
|
|
119
|
-
if (!stat?.isSymbolicLink()) return null;
|
|
120
|
-
return readlink(path);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isInsideWorkspace(workspacePath: string, path: string) {
|
|
124
|
-
const workspaceRoot = resolve(workspacePath);
|
|
125
|
-
const resolvedPath = resolve(path);
|
|
126
|
-
return resolvedPath === workspaceRoot || resolvedPath.startsWith(`${workspaceRoot}${sep}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function isFormalTaskWorkspacePath(path: string) {
|
|
130
|
-
return resolve(path).split(/[\\/]+/).includes(".worktrees");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function ensureWorkspaceNodeModulesDirectory(workspacePath: string) {
|
|
134
|
-
const nodeModulesPath = join(workspacePath, "node_modules");
|
|
135
|
-
const symlinkTarget = await readlinkIfSymlink(nodeModulesPath);
|
|
136
|
-
if (symlinkTarget) {
|
|
137
|
-
const resolvedTarget = resolve(dirname(nodeModulesPath), symlinkTarget);
|
|
138
|
-
if (!isInsideWorkspace(workspacePath, resolvedTarget)) {
|
|
139
|
-
await unlink(nodeModulesPath);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
await mkdir(nodeModulesPath, { recursive: true });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export async function ensureWorkspacePackageLinks(workspacePath: string) {
|
|
146
|
-
const links = await collectWorkspacePackageLinks(workspacePath);
|
|
147
|
-
if (links.length === 0) {
|
|
148
|
-
return {
|
|
149
|
-
workspacePath,
|
|
150
|
-
links,
|
|
151
|
-
createdOrUpdated: 0,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
await ensureWorkspaceNodeModulesDirectory(workspacePath);
|
|
155
|
-
const results = await Promise.all(
|
|
156
|
-
links.map((link) => ensureWorkspacePackageLink(link))
|
|
157
|
-
);
|
|
158
|
-
const createdOrUpdated = results.filter(Boolean).length;
|
|
159
|
-
return {
|
|
160
|
-
workspacePath,
|
|
161
|
-
links,
|
|
162
|
-
createdOrUpdated,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export async function prepareGitTaskWorkspace(args: {
|
|
167
|
-
agentKey: string;
|
|
168
|
-
cwd?: string;
|
|
169
|
-
env?: EnvLike;
|
|
170
|
-
}): Promise<PreparedGitTaskWorkspace> {
|
|
171
|
-
if (args.env?.NOLO_LOCAL_WORKTREE) {
|
|
172
|
-
await ensureWorkspacePackageLinks(args.env.NOLO_LOCAL_WORKTREE);
|
|
173
|
-
return { path: args.env.NOLO_LOCAL_WORKTREE };
|
|
174
|
-
}
|
|
175
|
-
const cwd = args.cwd ?? process.cwd();
|
|
176
|
-
const root = resolve(await runGitForTaskWorkspace(["rev-parse", "--show-toplevel"], cwd));
|
|
177
|
-
if (isFormalTaskWorkspacePath(root)) {
|
|
178
|
-
await ensureWorkspacePackageLinks(root);
|
|
179
|
-
return { path: root };
|
|
180
|
-
}
|
|
181
|
-
const parent = join(root, ".worktrees");
|
|
182
|
-
const safeAgent = safeTaskWorkspacePathSegment(args.agentKey);
|
|
183
|
-
const taskId = createTaskWorkspaceId();
|
|
184
|
-
const workspacePath = join(parent, `nolo-agent-${safeAgent}-${taskId}`);
|
|
185
|
-
const branchName = `nolo-agent-${safeAgent}-${taskId}`;
|
|
186
|
-
await runGitForTaskWorkspace(["worktree", "add", workspacePath, "-b", branchName, "HEAD"], root);
|
|
187
|
-
await ensureWorkspacePackageLinks(workspacePath);
|
|
188
|
-
return { path: workspacePath, branchName };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function formatPreparedGitTaskWorkspace(workspace: PreparedGitTaskWorkspace) {
|
|
192
|
-
return `[nolo] task worktree: ${workspace.path}${workspace.branchName ? ` (branch ${workspace.branchName})` : ""}\n`;
|
|
193
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Runtime workspace sessions describe where code tools execute.
|
|
2
|
-
// They are intentionally separate from product Spaces (`spaceId`, shared content, membership).
|
|
3
|
-
export type WorkspaceSessionMode = "current" | "task-worktree";
|
|
4
|
-
|
|
5
|
-
export type PreparedTaskWorkspace = {
|
|
6
|
-
path: string;
|
|
7
|
-
branchName?: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type PrepareTaskWorkspace = (args: {
|
|
11
|
-
agentKey: string;
|
|
12
|
-
env?: Record<string, string | undefined>;
|
|
13
|
-
}) => Promise<PreparedTaskWorkspace>;
|
|
14
|
-
|
|
15
|
-
export type WorkspaceSession =
|
|
16
|
-
| {
|
|
17
|
-
kind: "current";
|
|
18
|
-
workspaceRoot: string;
|
|
19
|
-
explicitCwd: boolean;
|
|
20
|
-
}
|
|
21
|
-
| {
|
|
22
|
-
kind: "task-worktree";
|
|
23
|
-
workspaceRoot: string;
|
|
24
|
-
explicitCwd: false;
|
|
25
|
-
branchName?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export function createWorkspaceSession(args: {
|
|
29
|
-
cwd?: string;
|
|
30
|
-
defaultCwd?: string;
|
|
31
|
-
}): WorkspaceSession {
|
|
32
|
-
return {
|
|
33
|
-
kind: "current",
|
|
34
|
-
workspaceRoot: args.cwd ?? args.defaultCwd ?? process.cwd(),
|
|
35
|
-
explicitCwd: Boolean(args.cwd),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function activateWorkspaceSession(
|
|
40
|
-
session: WorkspaceSession,
|
|
41
|
-
args: {
|
|
42
|
-
agentKey: string;
|
|
43
|
-
mode: WorkspaceSessionMode;
|
|
44
|
-
env?: Record<string, string | undefined>;
|
|
45
|
-
prepareTaskWorkspace?: PrepareTaskWorkspace;
|
|
46
|
-
}
|
|
47
|
-
): Promise<WorkspaceSession> {
|
|
48
|
-
if (args.mode !== "task-worktree") return session;
|
|
49
|
-
if (session.kind === "task-worktree" || session.explicitCwd) return session;
|
|
50
|
-
if (!args.prepareTaskWorkspace) {
|
|
51
|
-
throw new Error("task-worktree workspace sessions require a prepareTaskWorkspace implementation.");
|
|
52
|
-
}
|
|
53
|
-
const prepared = await args.prepareTaskWorkspace({
|
|
54
|
-
agentKey: args.agentKey,
|
|
55
|
-
env: args.env,
|
|
56
|
-
});
|
|
57
|
-
return workspaceSessionFromTaskWorkspace(prepared);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function workspaceSessionFromTaskWorkspace(
|
|
61
|
-
workspace: PreparedTaskWorkspace
|
|
62
|
-
): WorkspaceSession {
|
|
63
|
-
return {
|
|
64
|
-
kind: "task-worktree",
|
|
65
|
-
workspaceRoot: workspace.path,
|
|
66
|
-
explicitCwd: false,
|
|
67
|
-
...(workspace.branchName ? { branchName: workspace.branchName } : {}),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function formatWorkspaceSessionActivation(session: WorkspaceSession) {
|
|
72
|
-
if (session.kind !== "task-worktree") return "";
|
|
73
|
-
return `[nolo] workspace session: task-worktree ${session.workspaceRoot}${
|
|
74
|
-
session.branchName ? ` (branch ${session.branchName})` : ""
|
|
75
|
-
}\n`;
|
|
76
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
ensureWorkspacePackageLinks,
|
|
3
|
-
formatPreparedGitTaskWorkspace as formatPreparedTaskWorktree,
|
|
4
|
-
prepareGitTaskWorkspace as prepareTaskWorktree,
|
|
5
|
-
} from "../../agent-runtime/taskWorkspace";
|
|
6
|
-
export type {
|
|
7
|
-
PreparedGitTaskWorkspace as PreparedTaskWorktree,
|
|
8
|
-
} from "../../agent-runtime/taskWorkspace";
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
activateWorkspaceSession,
|
|
3
|
-
createWorkspaceSession,
|
|
4
|
-
formatWorkspaceSessionActivation,
|
|
5
|
-
} from "../../agent-runtime/workspaceSession";
|
|
6
|
-
export type {
|
|
7
|
-
PrepareTaskWorkspace,
|
|
8
|
-
PreparedTaskWorkspace,
|
|
9
|
-
WorkspaceSession,
|
|
10
|
-
WorkspaceSessionMode,
|
|
11
|
-
} from "../../agent-runtime/workspaceSession";
|
package/client/taskWorktree.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
ensureWorkspacePackageLinks,
|
|
3
|
-
formatPreparedGitTaskWorkspace as formatPreparedTaskWorktree,
|
|
4
|
-
prepareGitTaskWorkspace as prepareTaskWorktree,
|
|
5
|
-
} from "../agent-runtime/taskWorkspace";
|
|
6
|
-
export type {
|
|
7
|
-
PreparedGitTaskWorkspace as PreparedTaskWorktree,
|
|
8
|
-
} from "../agent-runtime/taskWorkspace";
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
activateWorkspaceSession,
|
|
5
|
-
createWorkspaceSession,
|
|
6
|
-
formatWorkspaceSessionActivation,
|
|
7
|
-
} from "./workspaceSession";
|
|
8
|
-
|
|
9
|
-
describe("CLI workspace session", () => {
|
|
10
|
-
test("starts from an explicit workspace root without preparing a task worktree", async () => {
|
|
11
|
-
const session = createWorkspaceSession({ cwd: "/repo/current" });
|
|
12
|
-
|
|
13
|
-
const result = await activateWorkspaceSession(session, {
|
|
14
|
-
agentKey: "frontend",
|
|
15
|
-
mode: "task-worktree",
|
|
16
|
-
prepareTaskWorkspace: async () => {
|
|
17
|
-
throw new Error("should not prepare a task worktree for explicit cwd");
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
expect(result.workspaceRoot).toBe("/repo/current");
|
|
22
|
-
expect(result.kind).toBe("current");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("activates a task worktree once when requested by agent policy", async () => {
|
|
26
|
-
const calls: string[] = [];
|
|
27
|
-
let session = createWorkspaceSession({ cwd: undefined });
|
|
28
|
-
|
|
29
|
-
session = await activateWorkspaceSession(session, {
|
|
30
|
-
agentKey: "agent-user-1-frontend",
|
|
31
|
-
mode: "task-worktree",
|
|
32
|
-
prepareTaskWorkspace: async ({ agentKey }) => {
|
|
33
|
-
calls.push(agentKey);
|
|
34
|
-
return {
|
|
35
|
-
path: "/repo/.worktrees/nolo-agent-frontend",
|
|
36
|
-
branchName: "nolo-agent-frontend",
|
|
37
|
-
};
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
session = await activateWorkspaceSession(session, {
|
|
41
|
-
agentKey: "agent-user-1-frontend",
|
|
42
|
-
mode: "task-worktree",
|
|
43
|
-
prepareTaskWorkspace: async () => {
|
|
44
|
-
throw new Error("task worktree should only be prepared once");
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
expect(calls).toEqual(["agent-user-1-frontend"]);
|
|
49
|
-
expect(session).toMatchObject({
|
|
50
|
-
kind: "task-worktree",
|
|
51
|
-
workspaceRoot: "/repo/.worktrees/nolo-agent-frontend",
|
|
52
|
-
branchName: "nolo-agent-frontend",
|
|
53
|
-
});
|
|
54
|
-
expect(formatWorkspaceSessionActivation(session)).toContain("workspace session: task-worktree");
|
|
55
|
-
expect(formatWorkspaceSessionActivation(session)).toContain("/repo/.worktrees/nolo-agent-frontend");
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
activateWorkspaceSession,
|
|
3
|
-
createWorkspaceSession,
|
|
4
|
-
formatWorkspaceSessionActivation,
|
|
5
|
-
} from "../agent-runtime/workspaceSession";
|
|
6
|
-
export type {
|
|
7
|
-
PrepareTaskWorkspace,
|
|
8
|
-
PreparedTaskWorkspace,
|
|
9
|
-
WorkspaceSession,
|
|
10
|
-
WorkspaceSessionMode,
|
|
11
|
-
} from "../agent-runtime/workspaceSession";
|