@relay-harness/coding-agent 1.0.2 → 1.0.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +17 -6
  2. package/LICENSE +22 -0
  3. package/README.md +12 -13
  4. package/dist/bundle/chunks/{chunk-XSLAMYCK.js → chunk-5H4IRYOE.js} +555 -61
  5. package/dist/bundle/chunks/{chunk-H6S6DQTM.js → chunk-NS67SA75.js} +1 -1
  6. package/dist/bundle/chunks/{virtual-modules-FNVEVB65.js → virtual-modules-ZGB2J7DY.js} +1 -1
  7. package/dist/bundle/cli-runtime.js +1 -1
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/bundle/rpc-entry.js +1 -1
  10. package/dist/core/settings-manager.d.ts +5 -3
  11. package/dist/core/settings-manager.d.ts.map +1 -1
  12. package/dist/core/settings-manager.js.map +1 -1
  13. package/dist/extensions/laya/assessment.d.ts +7 -1
  14. package/dist/extensions/laya/assessment.d.ts.map +1 -1
  15. package/dist/extensions/laya/assessment.js +21 -1
  16. package/dist/extensions/laya/assessment.js.map +1 -1
  17. package/dist/extensions/laya/docker.d.ts +19 -0
  18. package/dist/extensions/laya/docker.d.ts.map +1 -0
  19. package/dist/extensions/laya/docker.js +44 -0
  20. package/dist/extensions/laya/docker.js.map +1 -0
  21. package/dist/extensions/laya/index.d.ts +12 -3
  22. package/dist/extensions/laya/index.d.ts.map +1 -1
  23. package/dist/extensions/laya/index.js +331 -67
  24. package/dist/extensions/laya/index.js.map +1 -1
  25. package/dist/extensions/laya/learn.d.ts +67 -0
  26. package/dist/extensions/laya/learn.d.ts.map +1 -0
  27. package/dist/extensions/laya/learn.js +263 -0
  28. package/dist/extensions/laya/learn.js.map +1 -0
  29. package/dist/extensions/laya/memory.d.ts +48 -0
  30. package/dist/extensions/laya/memory.d.ts.map +1 -0
  31. package/dist/extensions/laya/memory.js +107 -0
  32. package/dist/extensions/laya/memory.js.map +1 -0
  33. package/dist/extensions/laya/model-manifest.d.ts +1 -1
  34. package/dist/extensions/laya/model-manifest.js +1 -1
  35. package/dist/extensions/laya/model-manifest.js.map +1 -1
  36. package/dist/extensions/laya/routers.d.ts +8 -0
  37. package/dist/extensions/laya/routers.d.ts.map +1 -1
  38. package/dist/extensions/laya/routers.js +2 -0
  39. package/dist/extensions/laya/routers.js.map +1 -1
  40. package/dist/extensions/laya/runtime.d.ts +39 -49
  41. package/dist/extensions/laya/runtime.d.ts.map +1 -1
  42. package/dist/extensions/laya/runtime.js +93 -145
  43. package/dist/extensions/laya/runtime.js.map +1 -1
  44. package/dist/extensions/laya/server.d.ts +39 -15
  45. package/dist/extensions/laya/server.d.ts.map +1 -1
  46. package/dist/extensions/laya/server.js +212 -78
  47. package/dist/extensions/laya/server.js.map +1 -1
  48. package/dist/extensions/laya/train-script.d.ts +8 -0
  49. package/dist/extensions/laya/train-script.d.ts.map +1 -0
  50. package/dist/extensions/laya/train-script.js +422 -0
  51. package/dist/extensions/laya/train-script.js.map +1 -0
  52. package/dist/extensions/laya/training.d.ts +176 -0
  53. package/dist/extensions/laya/training.d.ts.map +1 -0
  54. package/dist/extensions/laya/training.js +337 -0
  55. package/dist/extensions/laya/training.js.map +1 -0
  56. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/config-selector.js +3 -2
  58. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  59. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  60. package/dist/modes/interactive/interactive-mode.js +4 -4
  61. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  62. package/dist/utils/version-check.d.ts.map +1 -1
  63. package/dist/utils/version-check.js +10 -6
  64. package/dist/utils/version-check.js.map +1 -1
  65. package/docs/laya.md +59 -19
  66. package/docs/quickstart.md +19 -6
  67. package/docs/settings.md +4 -3
  68. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  69. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  70. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  71. package/examples/extensions/sandbox/package-lock.json +2 -2
  72. package/examples/extensions/sandbox/package.json +1 -1
  73. package/examples/extensions/with-deps/package-lock.json +2 -2
  74. package/examples/extensions/with-deps/package.json +1 -1
  75. package/package.json +15 -11
@@ -1 +1 @@
1
- {"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiB9D;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpG;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAsB,qBAAqB,CAC1C,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACnD,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAuBzC;AAED,wBAAsB,qBAAqB,CAC1C,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACnD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED,wBAAsB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAY7G"}
1
+ {"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiB9D;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpG;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAsB,qBAAqB,CAC1C,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACnD,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAuBzC;AAED,wBAAsB,qBAAqB,CAC1C,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACnD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED,wBAAsB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAY7G"}
@@ -1,7 +1,11 @@
1
1
  import { compare, valid } from "semver";
2
2
  import { fetchWithRetry } from "./management-http.js";
3
3
  import { getRelayUserAgent } from "./relay-user-agent.js";
4
- const LATEST_RELEASE_URL = "https://api.github.com/repos/eltonssouza/relay-harness/releases/latest";
4
+ /**
5
+ * The npm registry is public, so every install can read it. The GitHub releases API answers 404 for a
6
+ * private repository, which would silence the update notice for everyone.
7
+ */
8
+ const LATEST_VERSION_URL = "https://registry.npmjs.org/@relay-harness/coding-agent/latest";
5
9
  const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;
6
10
  /** Include useful errno details hidden behind Node's generic "fetch failed" error. */
7
11
  export function formatVersionCheckError(error) {
@@ -36,22 +40,22 @@ export function isNewerPackageVersion(candidateVersion, currentVersion) {
36
40
  export async function getLatestRelayRelease(currentVersion, options = {}) {
37
41
  if (process.env.RELAY_OFFLINE)
38
42
  return undefined;
39
- const response = await fetchWithRetry(LATEST_RELEASE_URL, {
43
+ const response = await fetchWithRetry(LATEST_VERSION_URL, {
40
44
  headers: {
41
45
  "User-Agent": getRelayUserAgent(currentVersion),
42
- accept: "application/vnd.github+json",
46
+ accept: "application/json",
43
47
  },
44
48
  }, {
45
49
  maxRetries: options.retry ? 2 : 0,
46
50
  timeoutMs: options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS,
47
51
  });
48
- // GitHub answers 404 when the repository has no published release yet.
52
+ // The registry answers 404 when the package is not published.
49
53
  if (!response.ok)
50
54
  return undefined;
51
55
  const data = (await response.json());
52
- if (typeof data?.tag_name !== "string")
56
+ if (typeof data?.version !== "string")
53
57
  return undefined;
54
- const version = valid(data.tag_name.trim().replace(/^v/, ""));
58
+ const version = valid(data.version.trim());
55
59
  return version ? { version } : undefined;
56
60
  }
57
61
  export async function getLatestRelayVersion(currentVersion, options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { compare, valid } from \"semver\";\nimport { fetchWithRetry } from \"./management-http.ts\";\nimport { getRelayUserAgent } from \"./relay-user-agent.ts\";\n\nconst LATEST_RELEASE_URL = \"https://api.github.com/repos/eltonssouza/relay-harness/releases/latest\";\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\n\nexport interface LatestRelayRelease {\n\tversion: string;\n}\n\n/** Include useful errno details hidden behind Node's generic \"fetch failed\" error. */\nexport function formatVersionCheckError(error: unknown): string {\n\tconst rootMessage = error instanceof Error && error.message ? error.message : String(error);\n\tconst cause = error instanceof Error ? error.cause : undefined;\n\tconst causes = cause instanceof AggregateError ? cause.errors : cause === undefined ? [] : [cause];\n\tconst codes = causes\n\t\t.map((value) =>\n\t\t\ttypeof value === \"object\" && value !== null && \"code\" in value && typeof value.code === \"string\"\n\t\t\t\t? value.code\n\t\t\t\t: undefined,\n\t\t)\n\t\t.filter((code): code is string => code !== undefined);\n\n\tif (codes.length > 0) return `${rootMessage} (${[...new Set(codes)].join(\", \")})`;\n\tconst causeMessage = causes.find(\n\t\t(value): value is Error => value instanceof Error && Boolean(value.message),\n\t)?.message;\n\treturn causeMessage ? `${rootMessage} (cause: ${causeMessage})` : rootMessage;\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = valid(leftVersion.trim());\n\tconst right = valid(rightVersion.trim());\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\treturn compare(left, right);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport async function getLatestRelayRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number; retry?: boolean } = {},\n): Promise<LatestRelayRelease | undefined> {\n\tif (process.env.RELAY_OFFLINE) return undefined;\n\n\tconst response = await fetchWithRetry(\n\t\tLATEST_RELEASE_URL,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"User-Agent\": getRelayUserAgent(currentVersion),\n\t\t\t\taccept: \"application/vnd.github+json\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmaxRetries: options.retry ? 2 : 0,\n\t\t\ttimeoutMs: options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS,\n\t\t},\n\t);\n\t// GitHub answers 404 when the repository has no published release yet.\n\tif (!response.ok) return undefined;\n\n\tconst data = (await response.json()) as { tag_name?: unknown } | null;\n\tif (typeof data?.tag_name !== \"string\") return undefined;\n\tconst version = valid(data.tag_name.trim().replace(/^v/, \"\"));\n\treturn version ? { version } : undefined;\n}\n\nexport async function getLatestRelayVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number; retry?: boolean } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestRelayRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewRelayVersion(currentVersion: string): Promise<LatestRelayRelease | undefined> {\n\tif (process.env.RELAY_SKIP_VERSION_CHECK) return undefined;\n\n\ttry {\n\t\tconst latestRelease = await getLatestRelayRelease(currentVersion);\n\t\tif (latestRelease && isNewerPackageVersion(latestRelease.version, currentVersion)) {\n\t\t\treturn latestRelease;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;GAGG;AACH,MAAM,kBAAkB,GAAG,+DAA+D,CAAC;AAC3F,MAAM,gCAAgC,GAAG,KAAK,CAAC;AAM/C,sFAAsF;AACtF,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACrD,MAAM,WAAW,GAAG,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5F,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,MAAM,MAAM,GAAG,KAAK,YAAY,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACnG,MAAM,KAAK,GAAG,MAAM;SAClB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACd,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC/F,CAAC,CAAC,KAAK,CAAC,IAAI;QACZ,CAAC,CAAC,SAAS,CACZ;SACA,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAEvD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,GAAG,WAAW,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IAClF,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC/B,CAAC,KAAK,EAAkB,EAAE,CAAC,KAAK,YAAY,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAC3E,EAAE,OAAO,CAAC;IACX,OAAO,YAAY,CAAC,CAAC,CAAC,GAAG,WAAW,YAAY,YAAY,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,WAAmB,EAAE,YAAoB;IAC/E,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,gBAAwB,EAAE,cAAsB;IACrF,MAAM,UAAU,GAAG,sBAAsB,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IAC5E,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,UAAU,GAAG,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,gBAAgB,CAAC,IAAI,EAAE,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,cAAsB,EACtB,OAAO,GAA4C,EAAE;IAErD,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IAEhD,MAAM,QAAQ,GAAG,MAAM,cAAc,CACpC,kBAAkB,EAClB;QACC,OAAO,EAAE;YACR,YAAY,EAAE,iBAAiB,CAAC,cAAc,CAAC;YAC/C,MAAM,EAAE,kBAAkB;SAC1B;KACD,EACD;QACC,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,gCAAgC;KAChE,CACD,CAAC;IACF,8DAA8D;IAC9D,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAEnC,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAiC,CAAC;IACrE,IAAI,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,cAAsB,EACtB,OAAO,GAA4C,EAAE;IAErD,OAAO,CAAC,MAAM,qBAAqB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,cAAsB;IACnE,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB;QAAE,OAAO,SAAS,CAAC;IAE3D,IAAI,CAAC;QACJ,MAAM,aAAa,GAAG,MAAM,qBAAqB,CAAC,cAAc,CAAC,CAAC;QAClE,IAAI,aAAa,IAAI,qBAAqB,CAAC,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CAAC;YACnF,OAAO,aAAa,CAAC;QACtB,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC","sourcesContent":["import { compare, valid } from \"semver\";\nimport { fetchWithRetry } from \"./management-http.ts\";\nimport { getRelayUserAgent } from \"./relay-user-agent.ts\";\n\n/**\n * The npm registry is public, so every install can read it. The GitHub releases API answers 404 for a\n * private repository, which would silence the update notice for everyone.\n */\nconst LATEST_VERSION_URL = \"https://registry.npmjs.org/@relay-harness/coding-agent/latest\";\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\n\nexport interface LatestRelayRelease {\n\tversion: string;\n}\n\n/** Include useful errno details hidden behind Node's generic \"fetch failed\" error. */\nexport function formatVersionCheckError(error: unknown): string {\n\tconst rootMessage = error instanceof Error && error.message ? error.message : String(error);\n\tconst cause = error instanceof Error ? error.cause : undefined;\n\tconst causes = cause instanceof AggregateError ? cause.errors : cause === undefined ? [] : [cause];\n\tconst codes = causes\n\t\t.map((value) =>\n\t\t\ttypeof value === \"object\" && value !== null && \"code\" in value && typeof value.code === \"string\"\n\t\t\t\t? value.code\n\t\t\t\t: undefined,\n\t\t)\n\t\t.filter((code): code is string => code !== undefined);\n\n\tif (codes.length > 0) return `${rootMessage} (${[...new Set(codes)].join(\", \")})`;\n\tconst causeMessage = causes.find(\n\t\t(value): value is Error => value instanceof Error && Boolean(value.message),\n\t)?.message;\n\treturn causeMessage ? `${rootMessage} (cause: ${causeMessage})` : rootMessage;\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = valid(leftVersion.trim());\n\tconst right = valid(rightVersion.trim());\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\treturn compare(left, right);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport async function getLatestRelayRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number; retry?: boolean } = {},\n): Promise<LatestRelayRelease | undefined> {\n\tif (process.env.RELAY_OFFLINE) return undefined;\n\n\tconst response = await fetchWithRetry(\n\t\tLATEST_VERSION_URL,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"User-Agent\": getRelayUserAgent(currentVersion),\n\t\t\t\taccept: \"application/json\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmaxRetries: options.retry ? 2 : 0,\n\t\t\ttimeoutMs: options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS,\n\t\t},\n\t);\n\t// The registry answers 404 when the package is not published.\n\tif (!response.ok) return undefined;\n\n\tconst data = (await response.json()) as { version?: unknown } | null;\n\tif (typeof data?.version !== \"string\") return undefined;\n\tconst version = valid(data.version.trim());\n\treturn version ? { version } : undefined;\n}\n\nexport async function getLatestRelayVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number; retry?: boolean } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestRelayRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewRelayVersion(currentVersion: string): Promise<LatestRelayRelease | undefined> {\n\tif (process.env.RELAY_SKIP_VERSION_CHECK) return undefined;\n\n\ttry {\n\t\tconst latestRelease = await getLatestRelayRelease(currentVersion);\n\t\tif (latestRelease && isNewerPackageVersion(latestRelease.version, currentVersion)) {\n\t\t\treturn latestRelease;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
package/docs/laya.md CHANGED
@@ -14,7 +14,7 @@ Relay normally runs every request on the model you selected. A typo fix then cos
14
14
 
15
15
  ## How a request is routed
16
16
 
17
- 1. **Task intelligence (Laya).** [Laya](https://github.com/NandhaKishorM/laya) is a small local classifier, the fast "System 1". It answers 18 typed questions about the request in one call: task type, complexity, scope, risk, ambiguity, reasoning needed, recommended capability tier and effort, agent role, validation level, the tools needed, and whether the task is security-sensitive. Relay calls it through the same System One protocol it uses for TypeSafe classifiers.
17
+ 1. **Task intelligence (Laya).** [Laya](https://github.com/NandhaKishorM/laya) is a small local classifier, the fast "System 1". It answers 18 typed questions about the request in one call: task type, complexity, scope, risk, ambiguity, reasoning needed, recommended capability tier and effort, agent role, validation level, the tools needed, and whether the task is security-sensitive. Relay calls it through the same System One protocol it uses for TypeSafe classifiers. A request close to a task learned with `/laya learn` takes that task's answers instead ([memory](#teach-laya-from-your-sessions)).
18
18
  2. **Policy engine.** Deterministic rules set floors Laya cannot lower. Production database migrations, authentication, security vulnerabilities and payment processing require at least the `strong` tier, an effort of at least `medium`, and review. High risk also requires `strong`. An answer whose confidence is below `laya.minConfidence` raises the tier by one.
19
19
  3. **Model registry.** Tiers are abstract (`fast`, `balanced`, `strong`, `frontier`), so Laya never learns model names. The registry maps each tier to concrete models; only models whose provider has credentials are candidates.
20
20
  4. **Selection.** Each candidate gets a utility from the cost profile: `quality × P(success) − cost × quota cost − latency × latency − risk × failure risk`. `P(success)` starts from a prior (0.90 when the model's tier is the required tier, +0.04 per tier above, −0.18 per tier below) and moves toward the observed success rate of that model on that task class. The best candidate whose success estimate reaches the profile minimum wins.
@@ -24,7 +24,7 @@ Relay normally runs every request on the model you selected. A typo fix then cos
24
24
 
25
25
  Reasoning effort follows Laya's recommendation up to `xhigh`. Routing never uses `max`. `laya/auto` has no thinking level of its own: a selected level would cap Laya's choice without you noticing. The footer shows the routed model and effort next to the selection, and the status line shows the task type and tier.
26
26
 
27
- When the Laya server is not running, keyword rules in English and Portuguese answer instead, and the status line shows `(rules)`. Laya is retried a minute later.
27
+ When the Laya server is not running (Docker is missing or stopped, or the image is still downloading), keyword rules in English and Portuguese answer instead, and the status line shows `(rules)`. Laya is retried a minute later.
28
28
 
29
29
  ## Cost profiles
30
30
 
@@ -59,35 +59,72 @@ Replace a tier with `laya.models`:
59
59
 
60
60
  `laya.quota` states how much of each subscription is left. Scarce quota makes a provider's models more expensive in the utility, so an equivalent model of another provider wins.
61
61
 
62
- ## Install the trained Laya
62
+ ## Laya runs in Docker
63
63
 
64
- The npm package does not contain the trained model (650 MB) or the Python environment it runs in (a few GB with torch). Relay fetches both once, into `~/.relay/agent/laya`:
64
+ Relay itself runs on your machine; Laya and its Python run only in Docker. The npm package contains neither the trained model (650 MB) nor Python with torch. Both come in a Docker image, `ghcr.io/eltonssouza/relay-laya`, built by Relay's release from the same sources as the package:
65
65
 
66
- | Piece | Where it comes from | Size |
66
+ | Image | Used when | Size |
67
67
  |---|---|---|
68
- | Trained model | The public Hugging Face repository [eltonssouza/relay-laya](https://huggingface.co/eltonssouza/relay-laya), pinned to one commit, every file checked against the sha256 in the package | about 680 MB |
69
- | Python environment | A venv created from Python 3.10 to 3.13, with `torch` (a CUDA build only when `nvidia-smi` finds a GPU, the CPU build otherwise), `laya[serve]`, `fastapi` and `uvicorn` | a few GB |
68
+ | `v1-cpu-<hash>` | No NVIDIA GPU | about 2 GB |
69
+ | `v1-cuda-<hash>` | `nvidia-smi` finds an NVIDIA GPU; training uses it | about 6 GB |
70
70
 
71
- The first time you send a request with `laya/auto` selected, Relay asks whether to install them. You can also run `/laya setup` at any time. After that, `laya/auto` starts the server on `127.0.0.1:8000` by itself when a request needs it and stops it when the session ends. A server that already answers on that port is used as is, so you can run your own. Until the install finishes, or when it is declined, keyword rules route requests.
71
+ The image holds Python 3.11, `torch`, `laya[serve]`, the shipped model from the Hugging Face repository [eltonssouza/relay-laya](https://huggingface.co/eltonssouza/relay-laya) (pinned to one commit, every file checked against the sha256 in the package), and the scripts that serve and train it. The tag ends in a hash of all of that, so each Relay version runs the image built from its own sources.
72
72
 
73
- Relay only starts a server for a local `laya.baseUrl`. Set `"laya": { "autostart": false }` to never start or offer one.
73
+ Install is automatic. Every time Relay starts in interactive mode it makes sure, in the background, that:
74
74
 
75
- Requirements: Python 3.10 to 3.13 (set `laya.python` if it is not on the PATH under `python3`, `python` or `py`), and on Windows a home directory path short enough for torch's DLLs (the default is).
75
+ 1. the image is present, pulling it the first time (the status line shows the layers);
76
+ 2. a container named `relay-laya` runs it with the active model, published on `127.0.0.1:8737` only (the port of `laya.baseUrl`), with `--restart unless-stopped`;
77
+ 3. the server answers.
76
78
 
77
- ## Train and serve Laya
79
+ The container then keeps running between sessions and comes back with Docker, so requests do not wait for the model to load. It serves on the CPU, which keeps the GPU free for training. Models trained from your sessions live in the Docker volume `relay-laya-models`. On the host, `~/.relay/agent/laya` only holds Relay's own data: the training exercises, the list of trained models, and telemetry. A Python environment or model left there by Relay 1.0.2 is deleted.
78
80
 
79
- Laya is trained with the `laya-trainer` Claude Code plugin. The questions Relay sends are the contract with the trained checkpoint, so train with Relay's own definitions:
81
+ Until the server answers, and whenever Docker is missing or stopped, keyword rules route requests; Relay says once why Laya is not running. Install [Docker Desktop](https://docs.docker.com/get-started/get-docker/) (Windows, macOS) or Docker Engine (Linux) and start it; the next Relay start installs Laya. `/laya setup` does it right away.
82
+
83
+ A server that already answers on the port when there is no `relay-laya` container is used as is, so you can run your own; one that announces other questions than Relay's, such as a laya-trainer server of another project, is reported instead of used. Relay manages a container only for a local `laya.baseUrl`. `"laya": { "autostart": false }` leaves Docker alone, and `laya.image` runs another image, for example one you built with `npm run laya:image -- --variant cpu` in a Relay checkout.
84
+
85
+ ## Teach Laya from your sessions
86
+
87
+ The shipped model learned from synthetic requests, so it routes your kind of work by rules of thumb. Teach it the tasks you actually do:
88
+
89
+ ```
90
+ /laya learn
91
+ ```
92
+
93
+ 1. **Collect.** Relay lists every request of the current session with the evidence of how it went: the model and effort that ran it, the tools called, the files changed, the commands, the errors of failed tool calls, whether tests and builds passed, what laya/auto planned, and your next message (a correction there means the task was not done right). `/laya learn <session file>` reads another session instead.
94
+ 2. **Label.** The agent answers Laya's 18 questions for each task from that evidence, for example "this fix needed the `balanced` tier, not `fast`: it took two failed edits and a correction". When the task taught something, it also writes a lesson: "The label comes from `locales/pt-BR/orders.json`, not the component; run `npm run i18n:check`." It saves them with the `laya_learn` tool and replies with a table of what it labeled. The labels are exercises in `~/.relay/agent/laya/training/data/dataset.jsonl`; a request learned again replaces its old labels.
95
+ 3. **Remember.** From the next request on, without waiting for training and even without Docker:
96
+ - A request close to a learned task is routed with that task's labels instead of Laya's or the keyword rules' answers. The status line shows `(memory)`. "Corrija o texto do botão Salvar na tela de pedidos, está cortado" goes to the tier and effort the learned "Corrija o texto do botão Salvar na tela de pedidos" needed, not to the cheapest tier the words "corrija o texto" suggest.
97
+ - The model that runs a request gets the lessons of similar learned tasks: in the laya/auto plan, or in a hidden `[laya:lessons]` message when you selected a model yourself.
98
+
99
+ Closeness is the cosine of the requests' word vectors, with words common to every coding request weighted down: routing needs 0.6, a lesson 0.45. Requests in another language than the learned one rarely match; training covers those. Turn the memory off with `"laya": { "memory": false }`.
100
+ 4. **Train.** Training runs in a Docker container, `relay-laya-train`, from the model that routes today. It mixes the session tasks with a replay sample of the earlier exercises, so Laya learns the new tasks without forgetting the rest. With an 8 GB NVIDIA GPU it takes about 7 minutes; the container gets the GPU when the CUDA image runs and Docker supports GPUs (Docker Desktop with WSL 2 on Windows, the NVIDIA Container Toolkit on Linux). Without one Relay asks first, because it can take an hour or more. The current model keeps routing while it runs, and the status line shows its progress.
101
+ 5. **Test and activate.** The new model and the current one answer the same held-out test, exercises neither was trained on. The new model routes requests from then on unless it answers more than 1% fewer test questions: Relay replaces the server container with one serving it, and reports how many answers on the session tasks it now gets right, before and after.
102
+
103
+ You can also ask in your own words ("learn from this conversation"): with [`tool_search`](cli.md#tools) active, the agent finds `laya_learn` itself.
104
+
105
+ | Command | Effect |
106
+ |---|---|
107
+ | `/laya learn [session file]` | Label the tasks of this session (or of a session file) and train on them |
108
+ | `/laya train` | Train again on every session task collected so far |
109
+ | `/laya models` | The shipped and trained models, their test scores, the exercises, and training progress |
110
+ | `/laya use <model>` | Route with another model, for example `/laya use v1` to go back to the shipped one |
111
+
112
+ Trained models live in the Docker volume `relay-laya-models` (about 680 MB each). Relay keeps the active one and the two newest others, and deletes older ones. Training needs Docker and the Laya image (`/laya setup`), and stops when Relay quits; it continues across `/new` and `/resume`.
113
+
114
+ ## Retrain from scratch
115
+
116
+ To change the questions or rebuild the model, train with the `laya-trainer` Claude Code plugin. The questions Relay sends are the contract with the trained checkpoint, so train with Relay's own definitions:
80
117
 
81
118
  1. `/laya-init` prepares the training tools.
82
119
  2. `/laya decisions` (in Relay) writes the questions to `.laya/decisions.json`.
83
- 3. `/laya seed 1100` writes synthetic bootstrap exercises to `.laya/data/relay-seed.jsonl`; add them with `/laya-data`. Their labels follow one rule from the task profile, so they teach Laya the policy, not evidence.
120
+ 3. `/laya seed 1100` writes synthetic bootstrap exercises to `.laya/data/relay-seed.jsonl`; add them with `/laya-data`. Their labels follow one rule from the task profile, so they teach Laya the policy, not evidence. The session tasks in `~/.relay/agent/laya/training/data/dataset.jsonl` (source `session`) can be added the same way.
84
121
  4. `/laya-train`, then `/laya-eval`.
85
122
 
86
- Over time, replace opinion with evidence: `/laya export` writes one exercise per successful routed request, labeled with the tier that finished it, to `.laya/data/relay-telemetry.jsonl`. Review it, add it with `/laya-data`, and train again. Telemetry stays local and contains your requests; turn it off with `"laya": { "telemetry": false }`.
123
+ `/laya export` writes one exercise per successful routed request, labeled only with the tier that finished it, to `.laya/data/relay-telemetry.jsonl`. Telemetry stays local and contains your requests; turn it off with `"laya": { "telemetry": false }`.
87
124
 
88
125
  ### Ship a new model
89
126
 
90
- A retrained model reaches users through a new revision of the Hugging Face repository and a new manifest in the package:
127
+ A retrained model reaches users through a new revision of the Hugging Face repository, a new manifest in the package, and the images the release builds from it:
91
128
 
92
129
  ```bash
93
130
  hf upload eltonssouza/relay-laya .laya/models/v2 . --commit-message "Laya model v2"
@@ -95,15 +132,18 @@ node scripts/package-laya-model.mjs --model .laya/models/v2 --version v2 --out /
95
132
  cp /tmp/laya-v2/model-manifest.ts packages/coding-agent/src/extensions/laya/model-manifest.ts
96
133
  ```
97
134
 
98
- `hf upload` needs `hf auth login` with a token that can write to the repository. Upload before the npm release that embeds the new manifest. The manifest pins the commit, file sizes and hashes, so a missing or altered file makes the install fail instead of running an unknown model. Installed users get the new version in a new directory on their next `/laya setup`.
135
+ `hf upload` needs `hf auth login` with a token that can write to the repository. Upload before the release that embeds the new manifest: the release workflow builds the images, which download the model files and fail on a missing or altered one, so an unknown model never ships. Users get the new image, and a container serving it, on their next Relay start.
99
136
 
100
137
  ## Commands
101
138
 
102
139
  | Command | Effect |
103
140
  |---|---|
104
- | `/laya` | Profile, runtime and server status, models with credentials, quota, and the last plan with its alternatives and escalations |
105
- | `/laya setup` | Install the Python environment and download the trained model |
106
- | `/laya start`, `/laya stop` | Start the local server, or stop the one this session started |
141
+ | `/laya` | Profile, container and server status, the routing model and training progress, models with credentials, quota, and the last plan with its alternatives and escalations |
142
+ | `/laya setup`, `/laya start` | Pull the image if needed and start or update the `relay-laya` container now |
143
+ | `/laya stop` | Stop the container; the next Relay start runs it again |
144
+ | `/laya learn [session file]` | Teach Laya the tasks of this session ([details](#teach-laya-from-your-sessions)) |
145
+ | `/laya train` | Train again on the session tasks collected so far |
146
+ | `/laya models`, `/laya use <model>` | List the shipped and trained models, or route with another one |
107
147
  | `/laya policy <profile>` | Cost profile for this session |
108
148
  | `/laya decisions [path]` | Write the questions in laya-trainer format |
109
149
  | `/laya seed [count] [path]` | Write synthetic training exercises |
@@ -6,7 +6,15 @@ For native Windows setup, read [Windows Setup](windows.md). For Android, read [T
6
6
 
7
7
  ## 1. Install Relay
8
8
 
9
- Relay is installed from source. It requires Node.js 22.19 or newer:
9
+ Relay requires Node.js 22.19 or newer. Install it from npm:
10
+
11
+ ```bash
12
+ npm install -g --ignore-scripts @relay-harness/coding-agent
13
+ ```
14
+
15
+ Relay does not require dependency lifecycle scripts, so `--ignore-scripts` is safe. To update Relay, run the same command again. `relay update` updates installed packages and model catalogs, not Relay itself.
16
+
17
+ To work on Relay itself, build it from a checkout instead:
10
18
 
11
19
  ```bash
12
20
  git clone https://github.com/eltonssouza/relay-harness.git
@@ -15,8 +23,6 @@ npm install --ignore-scripts
15
23
  npm run build
16
24
  ```
17
25
 
18
- Relay does not require dependency lifecycle scripts. To update Relay, pull the latest changes in the checkout and run `npm install --ignore-scripts` and `npm run build` again. `relay update` updates installed packages and model catalogs, not Relay itself.
19
-
20
26
  With Nix on macOS or Linux, install the latest release from Relay's flake. Nix builds Relay from source:
21
27
 
22
28
  ```bash
@@ -25,13 +31,14 @@ nix profile add github:eltonssouza/relay-harness/stable
25
31
 
26
32
  Older Nix versions use `nix profile install` instead. Update with `nix profile upgrade relay`. To pin a release, use a tag such as `github:eltonssouza/relay-harness/v1.0.0`.
27
33
 
28
- Verify the installation from the checkout:
34
+ Verify the installation:
29
35
 
30
36
  ```bash
31
- ./relay-test.sh --version # Linux and macOS
32
- ./relay-test.ps1 --version # Windows PowerShell
37
+ relay --version
33
38
  ```
34
39
 
40
+ From a source checkout, run `./relay-test.sh --version` (Linux and macOS) or `./relay-test.ps1 --version` (Windows PowerShell) instead.
41
+
35
42
  The examples below use `relay` for this command.
36
43
 
37
44
  ## 2. Start Relay
@@ -115,6 +122,12 @@ Start with the least powerful mechanism that meets your need:
115
122
 
116
123
  ## Uninstall Relay
117
124
 
125
+ If you installed Relay from npm, run:
126
+
127
+ ```bash
128
+ npm uninstall -g @relay-harness/coding-agent
129
+ ```
130
+
118
131
  If you built Relay from source, delete the checkout.
119
132
 
120
133
  If you installed Relay with Nix, run:
package/docs/settings.md CHANGED
@@ -102,9 +102,9 @@ Settings of the `laya/auto` model. See [Adaptive Execution with Laya](laya.md).
102
102
 
103
103
  | Setting | Type | Default | Description |
104
104
  |---|---|---|---|
105
- | `laya.baseUrl` | string | `LAYA_BASE_URL` or `http://127.0.0.1:8000/v1` | Laya System One server. |
106
- | `laya.autostart` | boolean | `true` | Offer to install the trained Laya on first use and start the local server when a request needs it. |
107
- | `laya.python` | string | First of `python3`, `python`, `py` | Python 3.10 to 3.13 used to create the Laya environment. |
105
+ | `laya.baseUrl` | string | `LAYA_BASE_URL` or `http://127.0.0.1:8737/v1` | Laya System One server. |
106
+ | `laya.autostart` | boolean | `true` | Install Laya in Docker when Relay starts and keep its container running. See [laya.md](laya.md#laya-runs-in-docker). |
107
+ | `laya.image` | string | The image built from this Relay version | Docker image of Laya, for example one built locally with `npm run laya:image`. |
108
108
  | `laya.policy` | string | `balanced` | Cost profile: `economy`, `balanced`, `quality` or `critical`. `--laya-policy` and `/laya policy` override it. |
109
109
  | `laya.models` | object | See [laya.md](laya.md#model-registry) | Models per tier (`fast`, `balanced`, `strong`, `frontier`) as `provider/model` lists in preference order. A tier you set replaces that tier's default list. |
110
110
  | `laya.quota` | object | `1` per provider | Share of each provider's quota still available, 0 to 1, e.g. `{ "openai-codex": 0.25 }`. |
@@ -112,6 +112,7 @@ Settings of the `laya/auto` model. See [Adaptive Execution with Laya](laya.md).
112
112
  | `laya.escalateAfterFailures` | number | `3` | Failed tool calls in one turn before moving to the next tier. `0` disables. |
113
113
  | `laya.toolRouting` | string | `advise` | `advise` names unneeded tools in the plan; `enforce` also deactivates them for the request. |
114
114
  | `laya.telemetry` | boolean | `true` | Record each routed request in `~/.relay/agent/laya/telemetry.jsonl`. |
115
+ | `laya.memory` | boolean | `true` | Route requests similar to tasks learned with `/laya learn` by their labels, and give the running model their lessons. See [laya.md](laya.md#teach-laya-from-your-sessions). |
115
116
  | `laya.timeoutMs` | number | `5000` | Classifier timeout before keyword rules answer instead. |
116
117
 
117
118
  ### Branch summaries
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "relay-extension-custom-provider",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "relay-extension-custom-provider",
9
- "version": "1.0.2",
9
+ "version": "1.0.3",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relay-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relay-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "relay-extension-sandbox",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "relay-extension-sandbox",
9
- "version": "1.0.2",
9
+ "version": "1.0.3",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relay-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "relay-extension-with-deps",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "relay-extension-with-deps",
9
- "version": "1.0.2",
9
+ "version": "1.0.3",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relay-extension-with-deps",
3
3
  "private": true,
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relay-harness/coding-agent",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "relayConfig": {
@@ -47,12 +47,12 @@
47
47
  "prepublishOnly": "npm run clean && npm run build"
48
48
  },
49
49
  "dependencies": {
50
- "@relay-harness/chord": "^1.0.2",
51
- "@relay-harness/agent-core": "^1.0.2",
52
- "@relay-harness/ai": "^1.0.2",
53
- "@relay-harness/codemode": "^1.0.2",
54
- "@relay-harness/mcp": "^1.0.2",
55
- "@relay-harness/tui": "^1.0.2",
50
+ "@relay-harness/chord": "^1.0.3",
51
+ "@relay-harness/agent-core": "^1.0.3",
52
+ "@relay-harness/ai": "^1.0.3",
53
+ "@relay-harness/codemode": "^1.0.3",
54
+ "@relay-harness/mcp": "^1.0.3",
55
+ "@relay-harness/tui": "^1.0.3",
56
56
  "@silvia-odwyer/photon-node": "0.3.4",
57
57
  "brace-expansion": "5.0.12",
58
58
  "chalk": "6.0.0",
@@ -75,9 +75,9 @@
75
75
  "protobufjs": "7.6.6"
76
76
  },
77
77
  "devDependencies": {
78
- "@relay-harness/client": "^1.0.2",
79
- "@relay-harness/protocol": "^1.0.2",
80
- "@relay-harness/server": "^1.0.2",
78
+ "@relay-harness/client": "^1.0.3",
79
+ "@relay-harness/protocol": "^1.0.3",
80
+ "@relay-harness/server": "^1.0.3",
81
81
  "@types/cross-spawn": "6.0.6",
82
82
  "@types/hosted-git-info": "3.0.5",
83
83
  "@types/node": "22.19.19",
@@ -95,7 +95,11 @@
95
95
  "tui",
96
96
  "agent"
97
97
  ],
98
- "author": "Mario Zechner",
98
+ "author": "Elton Souza",
99
+ "contributors": [
100
+ "Mario Zechner",
101
+ "Earendil Works"
102
+ ],
99
103
  "license": "MIT",
100
104
  "repository": {
101
105
  "type": "git",