@voxslopuli/openagentsy 0.0.0-stage → 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.
Files changed (168) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +21 -2
  3. package/dist/adapter-dependency.test.d.ts +2 -0
  4. package/dist/adapter-dependency.test.d.ts.map +1 -0
  5. package/dist/adapter-dependency.test.js +51 -0
  6. package/dist/adapter-dependency.test.js.map +1 -0
  7. package/dist/commands/approve.d.ts +63 -0
  8. package/dist/commands/approve.d.ts.map +1 -0
  9. package/dist/commands/approve.js +218 -0
  10. package/dist/commands/approve.js.map +1 -0
  11. package/dist/commands/approve.test.d.ts +2 -0
  12. package/dist/commands/approve.test.d.ts.map +1 -0
  13. package/dist/commands/approve.test.js +219 -0
  14. package/dist/commands/approve.test.js.map +1 -0
  15. package/dist/commands/audit.d.ts +74 -0
  16. package/dist/commands/audit.d.ts.map +1 -0
  17. package/dist/commands/audit.js +230 -0
  18. package/dist/commands/audit.js.map +1 -0
  19. package/dist/commands/audit.test.d.ts +2 -0
  20. package/dist/commands/audit.test.d.ts.map +1 -0
  21. package/dist/commands/audit.test.js +194 -0
  22. package/dist/commands/audit.test.js.map +1 -0
  23. package/dist/commands/board.d.ts +33 -0
  24. package/dist/commands/board.d.ts.map +1 -0
  25. package/dist/commands/board.js +65 -0
  26. package/dist/commands/board.js.map +1 -0
  27. package/dist/commands/build.d.ts +17 -0
  28. package/dist/commands/build.d.ts.map +1 -0
  29. package/dist/commands/build.js +145 -0
  30. package/dist/commands/build.js.map +1 -0
  31. package/dist/commands/conformance.d.ts +31 -0
  32. package/dist/commands/conformance.d.ts.map +1 -0
  33. package/dist/commands/conformance.js +57 -0
  34. package/dist/commands/conformance.js.map +1 -0
  35. package/dist/commands/conformance.test.d.ts +2 -0
  36. package/dist/commands/conformance.test.d.ts.map +1 -0
  37. package/dist/commands/conformance.test.js +74 -0
  38. package/dist/commands/conformance.test.js.map +1 -0
  39. package/dist/commands/doctor.d.ts +132 -0
  40. package/dist/commands/doctor.d.ts.map +1 -0
  41. package/dist/commands/doctor.js +768 -0
  42. package/dist/commands/doctor.js.map +1 -0
  43. package/dist/commands/doctor.test.d.ts +2 -0
  44. package/dist/commands/doctor.test.d.ts.map +1 -0
  45. package/dist/commands/doctor.test.js +329 -0
  46. package/dist/commands/doctor.test.js.map +1 -0
  47. package/dist/commands/ledger.d.ts +109 -0
  48. package/dist/commands/ledger.d.ts.map +1 -0
  49. package/dist/commands/ledger.js +286 -0
  50. package/dist/commands/ledger.js.map +1 -0
  51. package/dist/commands/ledger.test.d.ts +2 -0
  52. package/dist/commands/ledger.test.d.ts.map +1 -0
  53. package/dist/commands/ledger.test.js +238 -0
  54. package/dist/commands/ledger.test.js.map +1 -0
  55. package/dist/commands/lessons.d.ts +73 -0
  56. package/dist/commands/lessons.d.ts.map +1 -0
  57. package/dist/commands/lessons.js +97 -0
  58. package/dist/commands/lessons.js.map +1 -0
  59. package/dist/commands/lessons.test.d.ts +2 -0
  60. package/dist/commands/lessons.test.d.ts.map +1 -0
  61. package/dist/commands/lessons.test.js +146 -0
  62. package/dist/commands/lessons.test.js.map +1 -0
  63. package/dist/commands/models.d.ts +49 -0
  64. package/dist/commands/models.d.ts.map +1 -0
  65. package/dist/commands/models.js +125 -0
  66. package/dist/commands/models.js.map +1 -0
  67. package/dist/commands/report.d.ts +57 -0
  68. package/dist/commands/report.d.ts.map +1 -0
  69. package/dist/commands/report.js +164 -0
  70. package/dist/commands/report.js.map +1 -0
  71. package/dist/commands/report.test.d.ts +2 -0
  72. package/dist/commands/report.test.d.ts.map +1 -0
  73. package/dist/commands/report.test.js +294 -0
  74. package/dist/commands/report.test.js.map +1 -0
  75. package/dist/commands/scope.d.ts +34 -0
  76. package/dist/commands/scope.d.ts.map +1 -0
  77. package/dist/commands/scope.js +78 -0
  78. package/dist/commands/scope.js.map +1 -0
  79. package/dist/commands/scope.test.d.ts +2 -0
  80. package/dist/commands/scope.test.d.ts.map +1 -0
  81. package/dist/commands/scope.test.js +98 -0
  82. package/dist/commands/scope.test.js.map +1 -0
  83. package/dist/commands/setup.d.ts +31 -0
  84. package/dist/commands/setup.d.ts.map +1 -0
  85. package/dist/commands/setup.js +93 -0
  86. package/dist/commands/setup.js.map +1 -0
  87. package/dist/commands/waivers.d.ts +47 -0
  88. package/dist/commands/waivers.d.ts.map +1 -0
  89. package/dist/commands/waivers.js +68 -0
  90. package/dist/commands/waivers.js.map +1 -0
  91. package/dist/commands/waivers.test.d.ts +2 -0
  92. package/dist/commands/waivers.test.d.ts.map +1 -0
  93. package/dist/commands/waivers.test.js +64 -0
  94. package/dist/commands/waivers.test.js.map +1 -0
  95. package/dist/generate/agent.d.ts +19 -0
  96. package/dist/generate/agent.d.ts.map +1 -0
  97. package/dist/generate/agent.js +35 -0
  98. package/dist/generate/agent.js.map +1 -0
  99. package/dist/generate/agent.test.d.ts +2 -0
  100. package/dist/generate/agent.test.d.ts.map +1 -0
  101. package/dist/generate/agent.test.js +245 -0
  102. package/dist/generate/agent.test.js.map +1 -0
  103. package/dist/generate/commands.d.ts +15 -0
  104. package/dist/generate/commands.d.ts.map +1 -0
  105. package/dist/generate/commands.js +59 -0
  106. package/dist/generate/commands.js.map +1 -0
  107. package/dist/generate/lock.d.ts +18 -0
  108. package/dist/generate/lock.d.ts.map +1 -0
  109. package/dist/generate/lock.js +26 -0
  110. package/dist/generate/lock.js.map +1 -0
  111. package/dist/generate/prompt.d.ts +11 -0
  112. package/dist/generate/prompt.d.ts.map +1 -0
  113. package/dist/generate/prompt.js +114 -0
  114. package/dist/generate/prompt.js.map +1 -0
  115. package/dist/generate/prompt.test.d.ts +2 -0
  116. package/dist/generate/prompt.test.d.ts.map +1 -0
  117. package/dist/generate/prompt.test.js +59 -0
  118. package/dist/generate/prompt.test.js.map +1 -0
  119. package/dist/index.d.ts +40 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +448 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/index.test.d.ts +2 -0
  124. package/dist/index.test.d.ts.map +1 -0
  125. package/dist/index.test.js +237 -0
  126. package/dist/index.test.js.map +1 -0
  127. package/dist/opencode.d.ts +29 -0
  128. package/dist/opencode.d.ts.map +1 -0
  129. package/dist/opencode.js +125 -0
  130. package/dist/opencode.js.map +1 -0
  131. package/dist/plugin-bundle.test.d.ts +2 -0
  132. package/dist/plugin-bundle.test.d.ts.map +1 -0
  133. package/dist/plugin-bundle.test.js +43 -0
  134. package/dist/plugin-bundle.test.js.map +1 -0
  135. package/dist/plugin.d.ts +11 -0
  136. package/dist/plugin.d.ts.map +1 -0
  137. package/dist/plugin.js +46 -0
  138. package/dist/plugin.js.map +1 -0
  139. package/dist/quality-skills.test.d.ts +2 -0
  140. package/dist/quality-skills.test.d.ts.map +1 -0
  141. package/dist/quality-skills.test.js +141 -0
  142. package/dist/quality-skills.test.js.map +1 -0
  143. package/dist/report/footprint.d.ts +12 -0
  144. package/dist/report/footprint.d.ts.map +1 -0
  145. package/dist/report/footprint.js +121 -0
  146. package/dist/report/footprint.js.map +1 -0
  147. package/dist/report/ledger.d.ts +27 -0
  148. package/dist/report/ledger.d.ts.map +1 -0
  149. package/dist/report/ledger.js +197 -0
  150. package/dist/report/ledger.js.map +1 -0
  151. package/dist/report/oversight.d.ts +21 -0
  152. package/dist/report/oversight.d.ts.map +1 -0
  153. package/dist/report/oversight.js +146 -0
  154. package/dist/report/oversight.js.map +1 -0
  155. package/dist/report/policy-firing.test.d.ts +2 -0
  156. package/dist/report/policy-firing.test.d.ts.map +1 -0
  157. package/dist/report/policy-firing.test.js +68 -0
  158. package/dist/report/policy-firing.test.js.map +1 -0
  159. package/dist/report/scope.d.ts +64 -0
  160. package/dist/report/scope.d.ts.map +1 -0
  161. package/dist/report/scope.js +146 -0
  162. package/dist/report/scope.js.map +1 -0
  163. package/dist/who.d.ts +9 -0
  164. package/dist/who.d.ts.map +1 -0
  165. package/dist/who.js +11 -0
  166. package/dist/who.js.map +1 -0
  167. package/package.json +54 -4
  168. package/plugin/openagentsy.js +39478 -0
package/README.md CHANGED
@@ -1,3 +1,22 @@
1
- # Temporary Holding Version
1
+ # openagentsy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The command line: set the project up (`openagentsy init`), build the agent files from the roles (`openagentsy build`), pick the models (`openagentsy models`).
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ `npm install -g openagentsy`
9
+ ```
10
+
11
+
12
+ ## Development
13
+
14
+ This package is part of the OpenAgentsy monorepo and is developed there.
15
+
16
+ - Repository and docs: https://github.com/selfagency/openagentsy
17
+ - Changelog: https://github.com/selfagency/openagentsy/blob/main/CHANGELOG.md
18
+ - Report an issue: https://github.com/selfagency/openagentsy/issues
19
+
20
+ ## License
21
+
22
+ GPL-3.0-or-later.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=adapter-dependency.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-dependency.test.d.ts","sourceRoot":"","sources":["../src/adapter-dependency.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+ /**
6
+ * The published packages do not depend on the private adapter.
7
+ *
8
+ * The adapter is the OpenCode plugin: it is a file the CLI copies into a
9
+ * project's plugin directory, and it is never a package on npm. A published
10
+ * package that names it in its dependencies cannot be installed, because the
11
+ * dependency does not resolve, so the check walks the manifests of the
12
+ * workspace and fails when one of them names it.
13
+ *
14
+ * A devDependency is not a dependency. The CLI names the adapter as one,
15
+ * because the plugin file is built from it and copied into the package.
16
+ */
17
+ const ADAPTER = '@openagentsy/adapter-opencode';
18
+ /** The packages of the workspace, one directory each. */
19
+ const packagesRoot = fileURLToPath(new URL('../../', import.meta.url));
20
+ /** Whether a value is a manifest this check can read. */
21
+ function isManifest(value) {
22
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
23
+ }
24
+ describe('the published packages', () => {
25
+ it('do not depend on the private adapter', async () => {
26
+ const entries = await readdir(packagesRoot, { withFileTypes: true });
27
+ const offenders = [];
28
+ for (const entry of entries) {
29
+ if (!entry.isDirectory())
30
+ continue;
31
+ const raw = await readFile(join(packagesRoot, entry.name, 'package.json'), 'utf8').catch(() => undefined);
32
+ if (raw === undefined)
33
+ continue;
34
+ const parsed = JSON.parse(raw);
35
+ if (!isManifest(parsed))
36
+ continue;
37
+ if (parsed.private === true)
38
+ continue;
39
+ const dependencies = {
40
+ ...parsed.dependencies,
41
+ ...parsed.peerDependencies,
42
+ ...parsed.optionalDependencies,
43
+ };
44
+ const range = dependencies[ADAPTER];
45
+ if (range !== undefined)
46
+ offenders.push(`${parsed.name ?? entry.name} wants ${range}`);
47
+ }
48
+ expect(offenders).toEqual([]);
49
+ });
50
+ });
51
+ //# sourceMappingURL=adapter-dependency.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-dependency.test.js","sourceRoot":"","sources":["../src/adapter-dependency.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,GAAG,+BAA+B,CAAC;AAEhD,yDAAyD;AACzD,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAWvE,yDAAyD;AACzD,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;gBAAE,SAAS;YACnC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CACtF,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;YACF,IAAI,GAAG,KAAK,SAAS;gBAAE,SAAS;YAChC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAS;YAClC,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;gBAAE,SAAS;YACtC,MAAM,YAAY,GAAG;gBACnB,GAAG,MAAM,CAAC,YAAY;gBACtB,GAAG,MAAM,CAAC,gBAAgB;gBAC1B,GAAG,MAAM,CAAC,oBAAoB;aAC/B,CAAC;YACF,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACpC,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,UAAU,KAAK,EAAE,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,63 @@
1
+ /** What the command was asked to do. */
2
+ export type ApproveOptions = {
3
+ /** The state root: the approvals store and the audit live under it. */
4
+ stateRoot: string;
5
+ /** The call the grant covers, as the person typed it. */
6
+ callID?: string | undefined;
7
+ /** How long the grant lives, as the person typed it: milliseconds. */
8
+ ttl?: string | undefined;
9
+ /** The clock, so a test can move time. */
10
+ now?: number | undefined;
11
+ /** Who granted it. The person, unless the caller says otherwise. */
12
+ actor?: string | undefined;
13
+ };
14
+ /** What the command did. */
15
+ export type ApproveResult = {
16
+ ok: true;
17
+ outcome: 'granted';
18
+ callID: string;
19
+ expiresAt: number;
20
+ } | {
21
+ ok: true;
22
+ outcome: 'already-granted';
23
+ callID: string;
24
+ expiresAt: number;
25
+ } | {
26
+ ok: false;
27
+ outcome: 'no-call-id' | 'bad-ttl' | 'store-failed' | 'audit-failed' | 'unaudited-grant';
28
+ detail: string;
29
+ };
30
+ /**
31
+ * Grant a call the approval of a person.
32
+ *
33
+ * The grant is written down by the store, under its own lock, and the grant
34
+ * is audited. A call that is already granted is reported as granted and
35
+ * nothing is written: the store holds one entry per call, and the grant that
36
+ * is already there keeps the expiry it has.
37
+ */
38
+ export declare function runApprove(options: ApproveOptions): Promise<ApproveResult>;
39
+ /** What the consumer of a grant needs to spend it. */
40
+ export type UseApprovalOptions = {
41
+ /** The state root: the approvals store lives under it. */
42
+ stateRoot: string;
43
+ /** The call the grant covers. */
44
+ callID: string;
45
+ /** The agent that made the call. */
46
+ actor: string;
47
+ /** The clock, so a test can move time. */
48
+ now?: number | undefined;
49
+ };
50
+ /**
51
+ * Spend a grant: the call passes once, and the use is on the record.
52
+ *
53
+ * The store spends the grant under its own lock, so two callers cannot both
54
+ * pass the same call, and a grant that ran out is never spent. The store
55
+ * writes the trail of the spend with the agent that made the call, because
56
+ * the person who granted it is not the one that used it.
57
+ */
58
+ export declare function useApproval(options: UseApprovalOptions): Promise<boolean>;
59
+ /**
60
+ * The report, as a person reads it.
61
+ */
62
+ export declare function formatApproveResult(result: ApproveResult): string;
63
+ //# sourceMappingURL=approve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.d.ts","sourceRoot":"","sources":["../../src/commands/approve.ts"],"names":[],"mappings":"AAgEA,wCAAwC;AACxC,MAAM,MAAM,cAAc,GAAG;IAC3B,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC;AAEF,4BAA4B;AAC5B,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3E;IACE,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,cAAc,GAAG,cAAc,GAAG,iBAAiB,CAAC;IACxF,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiEN;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA+EhF;AAED,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAG/E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAOjE"}
@@ -0,0 +1,218 @@
1
+ import { join } from 'node:path';
2
+ import { appendAudit, POLICIES } from '@openagentsy/core';
3
+ import { createApprovalStore, DEFAULT_APPROVAL_TTL_MS, } from '@openagentsy/hooks';
4
+ /**
5
+ * The escape hatch: a person approves one call, and the call passes once.
6
+ *
7
+ * OpenCode's permission prompt cannot be answered from inside a plugin, so a
8
+ * destructive command is refused with the id of the call it refused. The
9
+ * person runs `/oa approve <callID>`, the grant is written down, and the
10
+ * call it names goes through exactly once.
11
+ *
12
+ * The store is the one in `@openagentsy/hooks`: the lock, the pruning of the
13
+ * grants that ran out and the one-shot consumption live there, and this
14
+ * module is the thin layer a person drives it through. What it adds is the
15
+ * audit: a grant and a use are both on the record, because an approval that
16
+ * leaves no trace is an approval nobody can look at afterwards.
17
+ *
18
+ * The store and the audit are the state root's, so a grant given in a
19
+ * worktree is a grant of the project.
20
+ */
21
+ /**
22
+ * Where the audit of a project lives.
23
+ *
24
+ * It is the board's own log, beside the board: the same file the leases and
25
+ * the records are audited in.
26
+ */
27
+ function auditPath(stateRoot) {
28
+ return join(stateRoot, '.openagentsy', 'audit.jsonl');
29
+ }
30
+ /**
31
+ * The actor of a grant: the person who ran the command.
32
+ *
33
+ * The grant is a human decision, and the board cannot tell who is calling it,
34
+ * so the name of the person is what the person says it is. The default says
35
+ * what it is without pretending to know more.
36
+ */
37
+ const HUMAN = 'human';
38
+ /**
39
+ * The longest a call id may be.
40
+ *
41
+ * The id comes from the host, so it is opaque, and the only thing a person
42
+ * can get wrong is a whole command instead of an id.
43
+ */
44
+ const MAX_CALL_ID = 200;
45
+ /**
46
+ * The longest a grant may live, in milliseconds.
47
+ *
48
+ * A day is generous: long enough for a person to read the reason the gate
49
+ * gave, answer it and let the agent retry, and short enough that a grant
50
+ * left behind does not open a door days later. A longer window is refused
51
+ * before anything is written, so a grant that never runs out cannot be
52
+ * written at all.
53
+ */
54
+ const MAX_TTL_MS = 24 * 60 * 60 * 1000;
55
+ /**
56
+ * Whether a string can be the id of a call.
57
+ *
58
+ * An id has no whitespace in it, and it names no path: a phrase, a path or
59
+ * a shell command is not an id, and the gate names the id in the reason it
60
+ * gives, so the person copies the id and not the sentence around it. The
61
+ * id is a JSON string today, and a charset that cannot name a path keeps it
62
+ * one that cannot.
63
+ */
64
+ function isCallID(text) {
65
+ if (text.length === 0 || text.length > MAX_CALL_ID)
66
+ return false;
67
+ if (/\s/.test(text))
68
+ return false;
69
+ if (text.includes('/') || text.includes('\\'))
70
+ return false;
71
+ if (text.includes('..'))
72
+ return false;
73
+ return true;
74
+ }
75
+ /**
76
+ * The lifetime a `--ttl` asks for, in milliseconds.
77
+ *
78
+ * The value is a number of milliseconds, and anything else is refused: a
79
+ * grant that lives for an unknown time is a grant that lives for ever, and
80
+ * so is one that lives for longer than a day.
81
+ */
82
+ function ttlOf(ttl) {
83
+ const text = ttl.trim();
84
+ if (!/^\d+$/.test(text))
85
+ return undefined;
86
+ const ms = Number(text);
87
+ if (!Number.isFinite(ms) || ms <= 0 || ms > MAX_TTL_MS)
88
+ return undefined;
89
+ return ms;
90
+ }
91
+ /**
92
+ * The message of an error, when it has one.
93
+ */
94
+ function describe(error) {
95
+ return error instanceof Error ? error.message : String(error);
96
+ }
97
+ /**
98
+ * The grant of a call, without throwing at the caller.
99
+ *
100
+ * The store holds its own lock, so the grant is written once: two people
101
+ * approving the same call at the same time do not write it twice.
102
+ */
103
+ async function grantCall(store, callID, now, ttlMs) {
104
+ try {
105
+ return { grant: await store.approve(callID, now, ttlMs) };
106
+ }
107
+ catch (error) {
108
+ return { error };
109
+ }
110
+ }
111
+ /**
112
+ * Grant a call the approval of a person.
113
+ *
114
+ * The grant is written down by the store, under its own lock, and the grant
115
+ * is audited. A call that is already granted is reported as granted and
116
+ * nothing is written: the store holds one entry per call, and the grant that
117
+ * is already there keeps the expiry it has.
118
+ */
119
+ export async function runApprove(options) {
120
+ const callID = options.callID ?? '';
121
+ if (!isCallID(callID)) {
122
+ return {
123
+ ok: false,
124
+ outcome: 'no-call-id',
125
+ detail: 'pass the id of the call to approve: /oa approve <callID>',
126
+ };
127
+ }
128
+ const ttlMs = options.ttl === undefined ? DEFAULT_APPROVAL_TTL_MS : ttlOf(options.ttl);
129
+ if (ttlMs === undefined) {
130
+ return {
131
+ ok: false,
132
+ outcome: 'bad-ttl',
133
+ detail: `the ttl is a number of milliseconds, at most ${MAX_TTL_MS} (a day): /oa approve <callID> --ttl <ms>`,
134
+ };
135
+ }
136
+ const now = options.now ?? Date.now();
137
+ const store = createApprovalStore(options.stateRoot);
138
+ const attempt = await grantCall(store, callID, now, ttlMs);
139
+ if ('error' in attempt) {
140
+ return {
141
+ ok: false,
142
+ outcome: 'store-failed',
143
+ detail: `the approval store could not be written: ${describe(attempt.error)}`,
144
+ };
145
+ }
146
+ const grant = attempt.grant;
147
+ if (!grant.created) {
148
+ return {
149
+ ok: true,
150
+ outcome: 'already-granted',
151
+ callID,
152
+ expiresAt: grant.entry.expiresAt,
153
+ };
154
+ }
155
+ // The grant is on file, and the trail is part of the grant: a grant that
156
+ // nobody can look at afterwards is a grant nobody gave.
157
+ try {
158
+ await appendAudit(auditPath(options.stateRoot), {
159
+ policyId: POLICIES.approveGranted.id,
160
+ policyVersion: POLICIES.approveGranted.version,
161
+ actor: options.actor ?? HUMAN,
162
+ action: 'approve',
163
+ resource: callID,
164
+ effect: 'allow',
165
+ detail: `the grant runs out at ${new Date(grant.entry.expiresAt).toISOString()}`,
166
+ });
167
+ }
168
+ catch (error) {
169
+ // The grant was written and the trail was not. The grant is taken back,
170
+ // so a call is never approved and not on the record at the same time.
171
+ let taken = false;
172
+ let failure;
173
+ try {
174
+ taken = await store.revokeApproval(callID, now);
175
+ }
176
+ catch (revokeError) {
177
+ failure = describe(revokeError);
178
+ }
179
+ if (taken) {
180
+ return {
181
+ ok: false,
182
+ outcome: 'audit-failed',
183
+ detail: `the audit could not be written, so the grant was taken back: ${describe(error)}`,
184
+ };
185
+ }
186
+ return {
187
+ ok: false,
188
+ outcome: 'unaudited-grant',
189
+ detail: `the grant was written to ${store.path} but the audit could not be written: ${describe(error)}. The call is approved and it is not on the record.${failure === undefined ? '' : ` The grant could not be taken back either: ${failure}.`}`,
190
+ };
191
+ }
192
+ return { ok: true, outcome: 'granted', callID, expiresAt: grant.entry.expiresAt };
193
+ }
194
+ /**
195
+ * Spend a grant: the call passes once, and the use is on the record.
196
+ *
197
+ * The store spends the grant under its own lock, so two callers cannot both
198
+ * pass the same call, and a grant that ran out is never spent. The store
199
+ * writes the trail of the spend with the agent that made the call, because
200
+ * the person who granted it is not the one that used it.
201
+ */
202
+ export async function useApproval(options) {
203
+ const store = createApprovalStore(options.stateRoot);
204
+ return store.consumeApproval(options.callID, options.now ?? Date.now(), options.actor);
205
+ }
206
+ /**
207
+ * The report, as a person reads it.
208
+ */
209
+ export function formatApproveResult(result) {
210
+ if (!result.ok)
211
+ return result.detail;
212
+ const until = new Date(result.expiresAt).toISOString();
213
+ if (result.outcome === 'already-granted') {
214
+ return `${result.callID} is already approved until ${until}. Nothing was changed.`;
215
+ }
216
+ return `approved ${result.callID} until ${until}`;
217
+ }
218
+ //# sourceMappingURL=approve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.js","sourceRoot":"","sources":["../../src/commands/approve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=approve.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.test.d.ts","sourceRoot":"","sources":["../../src/commands/approve.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,219 @@
1
+ import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { approvalGate, createApprovalStore, DEFAULT_APPROVAL_TTL_MS, } from '@openagentsy/hooks';
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
+ import { formatApproveResult, runApprove, useApproval } from './approve.js';
7
+ let root;
8
+ beforeEach(async () => {
9
+ root = await mkdtemp(join(tmpdir(), 'openagentsy-approve-'));
10
+ });
11
+ afterEach(async () => {
12
+ await rm(root, { recursive: true, force: true });
13
+ });
14
+ /** The grants the store holds, as they are written down. */
15
+ async function grants() {
16
+ const raw = await readFile(join(root, '.openagentsy', 'approvals.json'), 'utf8');
17
+ return JSON.parse(raw);
18
+ }
19
+ /** The audit lines the project holds. */
20
+ async function audit() {
21
+ const raw = await readFile(join(root, '.openagentsy', 'audit.jsonl'), 'utf8');
22
+ return raw
23
+ .split('\n')
24
+ .filter((line) => line.length > 0)
25
+ .map((line) => JSON.parse(line));
26
+ }
27
+ /** The reason a result carries, when it stops the call. */
28
+ function reasonOf(result) {
29
+ return 'reason' in result ? result.reason : undefined;
30
+ }
31
+ /** Whether a result lets the call through. */
32
+ function continues(result) {
33
+ return 'continue' in result ? result.continue : true;
34
+ }
35
+ /** The call the gate refuses, with the command it refuses. */
36
+ function destructiveCall(callID) {
37
+ return {
38
+ toolName: 'bash',
39
+ args: {},
40
+ files: [],
41
+ command: 'rm -rf /',
42
+ callID,
43
+ };
44
+ }
45
+ describe('the approve command', () => {
46
+ it('grants the call and writes the audit', async () => {
47
+ const result = await runApprove({ stateRoot: root, callID: 'call-1', now: 1000 });
48
+ expect(result.ok).toBe(true);
49
+ expect(result.outcome).toBe('granted');
50
+ expect(await grants()).toEqual([
51
+ { callID: 'call-1', expiresAt: 1000 + DEFAULT_APPROVAL_TTL_MS },
52
+ ]);
53
+ const lines = await audit();
54
+ expect(lines).toHaveLength(1);
55
+ expect(lines[0]).toMatchObject({
56
+ policyId: 'oa.approve.granted',
57
+ policyVersion: 'v1',
58
+ actor: 'human',
59
+ action: 'approve',
60
+ resource: 'call-1',
61
+ effect: 'allow',
62
+ });
63
+ });
64
+ it('reports what it granted and when it runs out', async () => {
65
+ const result = await runApprove({ stateRoot: root, callID: 'call-1', now: 1000 });
66
+ expect(formatApproveResult(result)).toBe(`approved call-1 until ${new Date(1000 + DEFAULT_APPROVAL_TTL_MS).toISOString()}`);
67
+ });
68
+ it('reports a call that is already granted and writes nothing twice', async () => {
69
+ await runApprove({ stateRoot: root, callID: 'call-1', now: 1000 });
70
+ const second = await runApprove({ stateRoot: root, callID: 'call-1', now: 2000 });
71
+ expect(second.ok).toBe(true);
72
+ expect(second.outcome).toBe('already-granted');
73
+ // The grant that is there keeps the expiry it has.
74
+ expect(await grants()).toEqual([
75
+ { callID: 'call-1', expiresAt: 1000 + DEFAULT_APPROVAL_TTL_MS },
76
+ ]);
77
+ expect(formatApproveResult(second)).toContain(new Date(1000 + DEFAULT_APPROVAL_TTL_MS).toISOString());
78
+ const lines = await audit();
79
+ expect(lines.filter((line) => line.policyId === 'oa.approve.granted')).toHaveLength(1);
80
+ });
81
+ it('grants a call once when many people approve it at the same time', async () => {
82
+ const results = await Promise.all([
83
+ runApprove({ stateRoot: root, callID: 'call-1', now: 1000 }),
84
+ runApprove({ stateRoot: root, callID: 'call-1', now: 1000 }),
85
+ runApprove({ stateRoot: root, callID: 'call-1', now: 1000 }),
86
+ runApprove({ stateRoot: root, callID: 'call-1', now: 1000 }),
87
+ ]);
88
+ expect(results.filter((result) => result.outcome === 'granted')).toHaveLength(1);
89
+ expect(await grants()).toHaveLength(1);
90
+ const lines = await audit();
91
+ expect(lines.filter((line) => line.policyId === 'oa.approve.granted')).toHaveLength(1);
92
+ });
93
+ it('refuses a command with no call id', async () => {
94
+ const result = await runApprove({ stateRoot: root });
95
+ expect(result.ok).toBe(false);
96
+ expect(result.outcome).toBe('no-call-id');
97
+ expect(formatApproveResult(result)).toContain('/oa approve <callID>');
98
+ });
99
+ it('refuses an argument that is not a call id', async () => {
100
+ const result = await runApprove({ stateRoot: root, callID: 'rm -rf /' });
101
+ expect(result.ok).toBe(false);
102
+ expect(result.outcome).toBe('no-call-id');
103
+ expect(formatApproveResult(result)).toContain('/oa approve <callID>');
104
+ });
105
+ it('refuses an argument that names a path', async () => {
106
+ for (const callID of ['a/../../b', '..', 'call/1', 'call\\1']) {
107
+ const result = await runApprove({ stateRoot: root, callID });
108
+ expect(result.ok, callID).toBe(false);
109
+ expect(result.outcome, callID).toBe('no-call-id');
110
+ }
111
+ });
112
+ it('refuses a ttl that is not a number of milliseconds', async () => {
113
+ const result = await runApprove({ stateRoot: root, callID: 'call-1', ttl: 'soon' });
114
+ expect(result.ok).toBe(false);
115
+ expect(result.outcome).toBe('bad-ttl');
116
+ expect(formatApproveResult(result)).toContain('--ttl <ms>');
117
+ });
118
+ it('refuses a ttl that is longer than a day', async () => {
119
+ const result = await runApprove({ stateRoot: root, callID: 'call-1', ttl: '86400001' });
120
+ expect(result.ok).toBe(false);
121
+ expect(result.outcome).toBe('bad-ttl');
122
+ expect(formatApproveResult(result)).toContain('86400000');
123
+ });
124
+ it('refuses a ttl so long that the grant would never run out', async () => {
125
+ const result = await runApprove({ stateRoot: root, callID: 'call-x', ttl: '9'.repeat(60) });
126
+ expect(result.ok).toBe(false);
127
+ expect(result.outcome).toBe('bad-ttl');
128
+ // Nothing was written: no grant on file, and no trail.
129
+ await expect(readFile(join(root, '.openagentsy', 'approvals.json'), 'utf8')).rejects.toThrow();
130
+ await expect(readFile(join(root, '.openagentsy', 'audit.jsonl'), 'utf8')).rejects.toThrow();
131
+ });
132
+ it('grants a call for the ttl the person asked for', async () => {
133
+ await runApprove({ stateRoot: root, callID: 'call-1', ttl: '60000', now: 1000 });
134
+ expect(await grants()).toEqual([{ callID: 'call-1', expiresAt: 61_000 }]);
135
+ });
136
+ it('takes the grant back when the audit cannot be written', async () => {
137
+ // The audit log is a directory, so nothing can be written to it.
138
+ await mkdir(join(root, '.openagentsy', 'audit.jsonl'), { recursive: true });
139
+ const result = await runApprove({ stateRoot: root, callID: 'call-1', now: 1000 });
140
+ expect(result.ok).toBe(false);
141
+ expect(result.outcome).toBe('audit-failed');
142
+ // The grant was taken back, so the call is not approved.
143
+ expect(await grants()).toEqual([]);
144
+ });
145
+ });
146
+ describe('the one-shot approval', () => {
147
+ it('lets the granted call through exactly once and refuses the second attempt', async () => {
148
+ await runApprove({ stateRoot: root, callID: 'call-7', now: 1000 });
149
+ const store = createApprovalStore(root);
150
+ const gate = approvalGate(/rm\s+-rf/, store, () => 2000);
151
+ const first = await gate(destructiveCall('call-7'));
152
+ expect(continues(first)).toBe(true);
153
+ expect(await grants()).toEqual([]);
154
+ const second = await gate(destructiveCall('call-7'));
155
+ expect(continues(second)).toBe(false);
156
+ expect(reasonOf(second)).toContain('/oa approve <callID>');
157
+ });
158
+ it('writes the trail of the spend when the gate lets a call through', async () => {
159
+ await runApprove({ stateRoot: root, callID: 'call-7', now: 1000 });
160
+ const store = createApprovalStore(root);
161
+ const gate = approvalGate(/rm\s+-rf/, store, () => 2000);
162
+ const result = await gate(destructiveCall('call-7'));
163
+ expect(continues(result)).toBe(true);
164
+ const lines = await audit();
165
+ const used = lines.filter((line) => line.policyId === 'oa.approve.used');
166
+ expect(used).toHaveLength(1);
167
+ expect(used[0]).toMatchObject({
168
+ policyId: 'oa.approve.used',
169
+ policyVersion: 'v1',
170
+ action: 'approve_use',
171
+ resource: 'call-7',
172
+ effect: 'allow',
173
+ });
174
+ });
175
+ it('spends the grant once and audits the use', async () => {
176
+ await runApprove({ stateRoot: root, callID: 'call-7', now: 1000 });
177
+ const spent = await useApproval({
178
+ stateRoot: root,
179
+ callID: 'call-7',
180
+ actor: 'oa-builder',
181
+ now: 2000,
182
+ });
183
+ expect(spent).toBe(true);
184
+ expect(await grants()).toEqual([]);
185
+ const lines = await audit();
186
+ const used = lines.filter((line) => line.policyId === 'oa.approve.used');
187
+ expect(used).toHaveLength(1);
188
+ expect(used[0]).toMatchObject({
189
+ policyId: 'oa.approve.used',
190
+ policyVersion: 'v1',
191
+ actor: 'oa-builder',
192
+ action: 'approve_use',
193
+ resource: 'call-7',
194
+ effect: 'allow',
195
+ });
196
+ });
197
+ it('refuses a second use of the same grant', async () => {
198
+ await runApprove({ stateRoot: root, callID: 'call-7', now: 1000 });
199
+ await useApproval({ stateRoot: root, callID: 'call-7', actor: 'oa-builder', now: 2000 });
200
+ const again = await useApproval({
201
+ stateRoot: root,
202
+ callID: 'call-7',
203
+ actor: 'oa-builder',
204
+ now: 2000,
205
+ });
206
+ expect(again).toBe(false);
207
+ const lines = await audit();
208
+ expect(lines.filter((line) => line.policyId === 'oa.approve.used')).toHaveLength(1);
209
+ });
210
+ it('never passes a grant that ran out', async () => {
211
+ await runApprove({ stateRoot: root, callID: 'call-7', ttl: '500', now: 1000 });
212
+ const store = createApprovalStore(root);
213
+ const gate = approvalGate(/rm\s+-rf/, store, () => 2000);
214
+ const result = await gate(destructiveCall('call-7'));
215
+ expect(continues(result)).toBe(false);
216
+ expect(await useApproval({ stateRoot: root, callID: 'call-7', actor: 'oa-builder', now: 2000 })).toBe(false);
217
+ });
218
+ });
219
+ //# sourceMappingURL=approve.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.test.js","sourceRoot":"","sources":["../../src/commands/approve.test.ts"],"names":[],"mappings":"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"}