@awak-app/simy-cli 0.2.3 → 0.3.3
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 +90 -1
- package/package.json +11 -4
- package/src/agent.js +723 -37
- package/src/bounded-local-task-subtask-pool.js +708 -0
- package/src/bounded-local-task-subtasks-contract.js +1189 -0
- package/src/cli-contract.js +42 -3
- package/src/console/app.js +212 -90
- package/src/desktop-executor.js +21 -2
- package/src/durable-local-task-steps-contract.js +1256 -0
- package/src/durable-local-task-worker.js +2607 -0
- package/src/execution-capability-contract.js +116 -0
- package/src/execution-guardrail.js +69 -12
- package/src/index.js +8 -7
- package/src/local-attachments.js +94 -113
- package/src/local-task-artifact-contract.js +266 -0
- package/src/local-task-attachment-store.js +447 -0
- package/src/local-task-file-capabilities.js +738 -0
- package/src/local-task-scenario-packs.js +681 -0
- package/src/local-task.js +1094 -0
- package/src/repository-inventory.js +37 -1
- package/src/shutdown.js +63 -0
- package/src/workspace-context.js +40 -7
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
lstat,
|
|
4
|
+
mkdir,
|
|
5
|
+
readFile,
|
|
6
|
+
readdir,
|
|
7
|
+
realpath,
|
|
8
|
+
writeFile,
|
|
9
|
+
} from "node:fs/promises";
|
|
3
10
|
import { homedir } from "node:os";
|
|
4
11
|
import { dirname, join, resolve } from "node:path";
|
|
5
12
|
import { promisify } from "node:util";
|
|
@@ -152,6 +159,35 @@ export function findRepository(inventory, repository) {
|
|
|
152
159
|
);
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
export async function verifyRepositoryIdentity(entry, expectedRepository) {
|
|
163
|
+
const expected = normalizeGitHubRemote(expectedRepository)?.toLowerCase();
|
|
164
|
+
const localPath = resolve(String(entry?.local_path || ""));
|
|
165
|
+
if (!expected || !localPath) return null;
|
|
166
|
+
try {
|
|
167
|
+
const details = await lstat(localPath);
|
|
168
|
+
if (!details.isDirectory() || details.isSymbolicLink()) return null;
|
|
169
|
+
const canonicalPath = await realpath(localPath);
|
|
170
|
+
const [{ stdout: root }, { stdout: remote }] = await Promise.all([
|
|
171
|
+
execFileAsync("git", ["rev-parse", "--show-toplevel"], { cwd: canonicalPath }),
|
|
172
|
+
execFileAsync("git", ["remote", "get-url", "origin"], { cwd: canonicalPath }),
|
|
173
|
+
]);
|
|
174
|
+
const canonicalRoot = await realpath(resolve(String(root || "").trim()));
|
|
175
|
+
if (
|
|
176
|
+
canonicalRoot !== canonicalPath ||
|
|
177
|
+
normalizeGitHubRemote(remote)?.toLowerCase() !== expected
|
|
178
|
+
) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
...entry,
|
|
183
|
+
repository: normalizeGitHubRemote(remote),
|
|
184
|
+
local_path: canonicalPath,
|
|
185
|
+
};
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
155
191
|
async function inspectGitRepository(directory) {
|
|
156
192
|
try {
|
|
157
193
|
const [{ stdout: root }, { stdout: remote }, { stdout: branch }] = await Promise.all([
|
package/src/shutdown.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
2
|
+
|
|
3
|
+
export function createAgentShutdown(agent) {
|
|
4
|
+
let shutdownPromise = null;
|
|
5
|
+
return () => {
|
|
6
|
+
if (!shutdownPromise) shutdownPromise = closeAgent(agent);
|
|
7
|
+
return shutdownPromise;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function closeAgent(agent) {
|
|
12
|
+
const serverClose = beginServerClose(agent?.server);
|
|
13
|
+
let shutdownError = null;
|
|
14
|
+
try {
|
|
15
|
+
await agent?.shutdown?.();
|
|
16
|
+
} catch (error) {
|
|
17
|
+
shutdownError = error;
|
|
18
|
+
} finally {
|
|
19
|
+
// server.close() stops new connections immediately, but its callback waits
|
|
20
|
+
// for active SSE and other long-lived requests. Drain Provider work and its
|
|
21
|
+
// final durable status first, then release those remaining connections.
|
|
22
|
+
agent?.server?.closeAllConnections?.();
|
|
23
|
+
}
|
|
24
|
+
await serverClose;
|
|
25
|
+
if (shutdownError) throw shutdownError;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function installAgentShutdownSignalHandlers({
|
|
29
|
+
signalSource = process,
|
|
30
|
+
shutdown,
|
|
31
|
+
exit = (code) => process.exit(code),
|
|
32
|
+
reportError = (error) => console.error(error instanceof Error ? error.message : String(error)),
|
|
33
|
+
} = {}) {
|
|
34
|
+
if (typeof shutdown !== "function") throw new TypeError("shutdown is required");
|
|
35
|
+
const handlers = new Map();
|
|
36
|
+
for (const signal of SHUTDOWN_SIGNALS) {
|
|
37
|
+
const handler = () => {
|
|
38
|
+
void shutdown()
|
|
39
|
+
.then(() => exit(0))
|
|
40
|
+
.catch((error) => {
|
|
41
|
+
reportError(error);
|
|
42
|
+
exit(1);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
handlers.set(signal, handler);
|
|
46
|
+
signalSource.once(signal, handler);
|
|
47
|
+
}
|
|
48
|
+
return () => {
|
|
49
|
+
for (const [signal, handler] of handlers) {
|
|
50
|
+
signalSource.off(signal, handler);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function beginServerClose(server) {
|
|
56
|
+
if (!server?.listening) return Promise.resolve();
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
server.close((error) => {
|
|
59
|
+
if (error) reject(error);
|
|
60
|
+
else resolve();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/src/workspace-context.js
CHANGED
|
@@ -23,13 +23,46 @@ export async function discoverWorkspace(cwd = process.cwd()) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function normalizeGitHubRemote(value) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
26
|
+
const remote = String(value || "").trim();
|
|
27
|
+
let pathname = null;
|
|
28
|
+
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?$/i.test(remote)) {
|
|
29
|
+
pathname = remote;
|
|
30
|
+
} else {
|
|
31
|
+
const scp = /^git@github\.com:([^?#]+)$/i.exec(remote);
|
|
32
|
+
if (scp) {
|
|
33
|
+
pathname = scp[1];
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(remote);
|
|
37
|
+
if (
|
|
38
|
+
!["https:", "http:", "ssh:"].includes(parsed.protocol) ||
|
|
39
|
+
parsed.hostname.toLowerCase() !== "github.com" ||
|
|
40
|
+
parsed.search ||
|
|
41
|
+
parsed.hash
|
|
42
|
+
) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
pathname = parsed.pathname.replace(/^\/+/, "");
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const repository = pathname.replace(/\.git$/i, "");
|
|
52
|
+
const parts = repository.split("/");
|
|
53
|
+
if (
|
|
54
|
+
parts.length !== 2 ||
|
|
55
|
+
parts.some(
|
|
56
|
+
(part) =>
|
|
57
|
+
!part ||
|
|
58
|
+
part === "." ||
|
|
59
|
+
part === ".." ||
|
|
60
|
+
!/^[A-Za-z0-9_.-]+$/.test(part),
|
|
61
|
+
)
|
|
62
|
+
) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return parts.join("/");
|
|
33
66
|
}
|
|
34
67
|
|
|
35
68
|
function emptyWorkspace(cwd) {
|