zigmini 1.0.0-beta.3 → 1.1.0-beta.2

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 (138) hide show
  1. package/README.ko.md +29 -2
  2. package/README.md +29 -2
  3. package/assets/agent/gateway-request-rules.md +9 -2
  4. package/assets/agent/gateway-rules.md +13 -6
  5. package/assets/android/zigmini.apk +0 -0
  6. package/assets/android/zigmini.apk.json +3 -3
  7. package/assets/build.json +1 -1
  8. package/assets/document-reader/document-reader +0 -0
  9. package/assets/document-reader/host-selector +0 -0
  10. package/assets/document-reader/manifest.json +27 -0
  11. package/docs/manual.ko.md +114 -10
  12. package/docs/manual.md +117 -11
  13. package/host/document-reader/DocumentRead.swift +370 -0
  14. package/host/document-reader/HostSelector.swift +192 -0
  15. package/host/document-reader/Protocol.swift +647 -0
  16. package/host/document-reader/main.swift +22 -0
  17. package/host/omo-bridge/agent-aside-tool.ts +119 -0
  18. package/host/omo-bridge/agent-aside.ts +522 -0
  19. package/host/omo-bridge/agent-collaboration-files.ts +282 -0
  20. package/host/omo-bridge/agent-collaboration.ts +385 -0
  21. package/host/omo-bridge/agent-control.ts +400 -28
  22. package/host/omo-bridge/agent-execution.sb +42 -0
  23. package/host/omo-bridge/agent-execution.ts +158 -0
  24. package/host/omo-bridge/agent-tool-adapters.ts +388 -0
  25. package/host/omo-bridge/agent-tool-permissions.ts +96 -0
  26. package/host/omo-bridge/aside-tabs.ts +41 -0
  27. package/host/omo-bridge/attachment-read.ts +100 -0
  28. package/host/omo-bridge/bridge.ts +10 -0
  29. package/host/omo-bridge/managed-call.ts +46 -0
  30. package/host/omo-bridge/task-control.ts +39 -5
  31. package/host/omo-check/patches/attachments-293/README.md +138 -0
  32. package/host/omo-check/patches/attachments-293/document-content.js +234 -0
  33. package/host/omo-check/patches/attachments-293/document-content.types.txt +91 -0
  34. package/host/omo-check/patches/attachments-293/omo-5.1.51-documents.patch +831 -0
  35. package/host/omo-check/patches/attachments-293/omo-5.1.51-gateway-port.manifest.json +471 -0
  36. package/host/omo-check/patches/omo-5.1.51-agent-tool-authority.patch +471 -0
  37. package/host/omo-check/patches/omo-5.1.51-gateway-port.manifest.json +28 -6
  38. package/host/omo-check/patches/omo-5.1.51-memory-receipts.patch +5 -0
  39. package/package.json +5 -3
  40. package/src/agent/api.ts +345 -20
  41. package/src/agent/artifacts.ts +196 -0
  42. package/src/agent/aside-target-routes.ts +369 -0
  43. package/src/agent/aside-targets.ts +215 -0
  44. package/src/agent/collaboration-admission.ts +47 -0
  45. package/src/agent/collaboration-read.ts +117 -0
  46. package/src/agent/collaboration-session.ts +196 -0
  47. package/src/agent/collaboration-types.ts +258 -0
  48. package/src/agent/collaboration.ts +399 -0
  49. package/src/agent/definition.ts +65 -3
  50. package/src/agent/fields.ts +9 -3
  51. package/src/agent/file-core.ts +325 -0
  52. package/src/agent/files.ts +87 -8
  53. package/src/agent/list.ts +3 -3
  54. package/src/agent/managed-call.ts +144 -0
  55. package/src/agent/tool-catalog.ts +184 -0
  56. package/src/agent/tool-delivery.ts +359 -0
  57. package/src/agent/tool-permission-types.ts +81 -0
  58. package/src/agent/tool-permissions.ts +1366 -0
  59. package/src/attachments/api.ts +161 -0
  60. package/src/attachments/document-reader.ts +325 -0
  61. package/src/attachments/host-selector.ts +410 -0
  62. package/src/attachments/managed-binding.ts +83 -0
  63. package/src/attachments/prepare.ts +214 -0
  64. package/src/attachments/selector-port.ts +28 -0
  65. package/src/attachments/service.ts +729 -0
  66. package/src/attachments/store.ts +698 -0
  67. package/src/attachments/types.ts +123 -0
  68. package/src/cli/doctor.ts +30 -10
  69. package/src/decisions.ts +109 -10
  70. package/src/engine/apply-patch.ts +21 -0
  71. package/src/engine/compat.json +2 -1
  72. package/src/engine/install.ts +45 -4
  73. package/src/engine/sandbox-cell.ts +2 -0
  74. package/src/gc.ts +33 -225
  75. package/src/housekeeping/api.ts +163 -0
  76. package/src/housekeeping/audit.ts +61 -0
  77. package/src/housekeeping/collectors/agent-collaboration.ts +33 -0
  78. package/src/housekeeping/collectors/agent-drafts.ts +43 -0
  79. package/src/housekeeping/collectors/agent-file-trash.ts +36 -0
  80. package/src/housekeeping/collectors/agent-trash.ts +28 -0
  81. package/src/housekeeping/collectors/attachment-cleanup.ts +35 -0
  82. package/src/housekeeping/collectors/db-backup.ts +25 -0
  83. package/src/housekeeping/collectors/docker-build-cache.ts +29 -0
  84. package/src/housekeeping/collectors/docker-image.ts +135 -0
  85. package/src/housekeeping/collectors/omo-version.ts +32 -0
  86. package/src/housekeeping/collectors/orphan-test-process.ts +205 -0
  87. package/src/housekeeping/collectors/reconcile-work.ts +55 -0
  88. package/src/housekeeping/collectors/rpc-daemon.ts +39 -0
  89. package/src/housekeeping/collectors/shared.ts +149 -0
  90. package/src/housekeeping/collectors/temp-dir.ts +28 -0
  91. package/src/housekeeping/collectors/update-file.ts +25 -0
  92. package/src/housekeeping/collectors/worktree.ts +193 -0
  93. package/src/housekeeping/engine.ts +125 -0
  94. package/src/housekeeping/reconcile/work.ts +130 -0
  95. package/src/housekeeping/registry.ts +63 -0
  96. package/src/housekeeping/trash.ts +59 -0
  97. package/src/housekeeping/types.ts +31 -0
  98. package/src/hub.ts +1095 -41
  99. package/src/main.ts +228 -30
  100. package/src/message-references.ts +14 -5
  101. package/src/profile-prompt.ts +13 -1
  102. package/src/protocol.ts +41 -6
  103. package/src/security-allow.ts +79 -2
  104. package/src/security-check.ts +1 -1
  105. package/src/store.ts +1946 -23
  106. package/src/test-crash.ts +20 -0
  107. package/src/work-watch.ts +55 -7
  108. package/src/work.ts +192 -3
  109. package/web/DESIGN.md +33 -0
  110. package/web/agents.d.ts +189 -12
  111. package/web/agents.js +898 -39
  112. package/web/app.js +2634 -210
  113. package/web/attachments.d.ts +91 -0
  114. package/web/attachments.js +422 -0
  115. package/web/collaboration.d.ts +87 -0
  116. package/web/collaboration.js +290 -0
  117. package/web/decisions.d.ts +39 -2
  118. package/web/decisions.js +354 -2
  119. package/web/gc.d.ts +26 -5
  120. package/web/gc.js +113 -13
  121. package/web/i18n/agents.d.ts +4 -0
  122. package/web/i18n/agents.js +200 -8
  123. package/web/i18n/attachments.js +78 -0
  124. package/web/i18n/decisions.d.ts +4 -0
  125. package/web/i18n/decisions.js +68 -0
  126. package/web/i18n/gc.d.ts +4 -0
  127. package/web/i18n/gc.js +184 -2
  128. package/web/i18n/main.js +80 -8
  129. package/web/i18n/tour.d.ts +4 -0
  130. package/web/i18n/tour.js +37 -0
  131. package/web/index.html +20 -3
  132. package/web/profile.d.ts +2 -2
  133. package/web/profile.js +11 -6
  134. package/web/style.css +342 -21
  135. package/web/tooltip.d.ts +2 -0
  136. package/web/tooltip.js +12 -0
  137. package/web/tour.d.ts +57 -0
  138. package/web/tour.js +450 -0
@@ -0,0 +1,192 @@
1
+ import Foundation
2
+ import Darwin
3
+ import CryptoKit
4
+
5
+ enum SelectorFailure: Error {
6
+ case access, changed, limit
7
+ var code: HelperFailureCode {
8
+ switch self {
9
+ case .access: return .hostAccessDenied
10
+ case .changed: return .fileChanged
11
+ case .limit: return .processingLimit
12
+ }
13
+ }
14
+ }
15
+
16
+ // Directory authority is a chain of descriptors, never a canonicalized pathname
17
+ // checked now and reopened later. No parser framework is linked to this entrypoint.
18
+ enum HostSelector {
19
+ private static func identity(_ value: stat) throws -> NativeIdentity {
20
+ NativeIdentity(device: String(value.st_dev), inode: String(value.st_ino),
21
+ size: Int(value.st_size),
22
+ mtimeNs: try NativeIdentity.nanoseconds(seconds: Int64(value.st_mtimespec.tv_sec), nanoseconds: value.st_mtimespec.tv_nsec),
23
+ ctimeNs: try NativeIdentity.nanoseconds(seconds: Int64(value.st_ctimespec.tv_sec), nanoseconds: value.st_ctimespec.tv_nsec),
24
+ nlink: Int(value.st_nlink))
25
+ }
26
+ private static func regular(_ value: stat) -> Bool {
27
+ value.st_mode & S_IFMT == S_IFREG && value.st_nlink == 1
28
+ }
29
+ private static func supported(_ name: String) -> Bool {
30
+ let suffix = (name as NSString).pathExtension.lowercased()
31
+ return ["html", "htm", "md", "markdown", "txt", "pdf"].contains(suffix)
32
+ }
33
+ private static func directory(_ parent: Int32, _ name: String) throws -> Int32 {
34
+ let fd = openat(parent, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC)
35
+ guard fd >= 0 else { throw SelectorFailure.access }
36
+ var value = stat()
37
+ guard fstat(fd, &value) == 0, value.st_mode & S_IFMT == S_IFDIR else {
38
+ close(fd); throw SelectorFailure.access
39
+ }
40
+ return fd
41
+ }
42
+ private static func root(_ root: SelectorRoot) throws -> Int32 {
43
+ guard let record = getpwuid(getuid()), let homePointer = record.pointee.pw_dir else { throw SelectorFailure.access }
44
+ #if HOST_SELECTOR_TESTING
45
+ // Compiled out of the shipping selector. Only the owned fixture build can
46
+ // replace trusted roots or subscribe to deterministic syscall boundaries.
47
+ guard let fixtureHome = ProcessInfo.processInfo.environment["HOST_SELECTOR_TEST_HOME"],
48
+ let fixtureComputer = ProcessInfo.processInfo.environment["HOST_SELECTOR_TEST_COMPUTER"] else {
49
+ throw SelectorFailure.access
50
+ }
51
+ let home = fixtureHome, computer = fixtureComputer
52
+ _ = homePointer
53
+ #else
54
+ let home = String(cString: homePointer), computer = "/"
55
+ #endif
56
+ let path = root == .computer ? computer : home
57
+ var fd = open("/", O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC)
58
+ guard fd >= 0, path.hasPrefix("/") else { if fd >= 0 { close(fd) }; throw SelectorFailure.access }
59
+ do {
60
+ for component in path.split(separator: "/") {
61
+ let next = try directory(fd, String(component)); close(fd); fd = next
62
+ }
63
+ if root == .downloads {
64
+ let next = try directory(fd, "Downloads"); close(fd); fd = next
65
+ }
66
+ return fd
67
+ } catch { close(fd); throw error }
68
+ }
69
+ private static func traverse(_ request: SelectorRequest, components: ArraySlice<String>) throws -> Int32 {
70
+ var fd = try root(request.root)
71
+ do {
72
+ for (index, component) in components.enumerated() {
73
+ try boundary("component.before:\(index)")
74
+ let next = try directory(fd, component); close(fd); fd = next
75
+ }
76
+ return fd
77
+ } catch { close(fd); throw error }
78
+ }
79
+ static func handle(_ request: SelectorRequest, emit: (HelperFrame) throws -> Void) throws {
80
+ try HelperCodec.validate(.selector(request))
81
+ switch request.operation {
82
+ case .list: try list(request, emit: emit)
83
+ case .snapshot: try snapshot(request, emit: emit)
84
+ }
85
+ }
86
+ private static func list(_ request: SelectorRequest, emit: (HelperFrame) throws -> Void) throws {
87
+ let fd = try traverse(request, components: request.segments[...])
88
+ guard let stream = fdopendir(fd) else { close(fd); throw SelectorFailure.access }
89
+ defer { closedir(stream) }
90
+ var entries: [NativeEntry] = []
91
+ let query = (request.query ?? "").lowercased()
92
+ // Keep at most the next page plus one lookahead, even for huge directories.
93
+ while true {
94
+ errno = 0
95
+ guard let pointer = readdir(stream) else {
96
+ guard errno == 0 else { throw SelectorFailure.access }
97
+ break
98
+ }
99
+ let name = withUnsafePointer(to: &pointer.pointee.d_name) {
100
+ $0.withMemoryRebound(to: CChar.self, capacity: Int(pointer.pointee.d_namlen) + 1) {
101
+ String(validatingCString: $0)
102
+ }
103
+ }
104
+ guard let name, name != ".", name != ".." else { continue }
105
+ if request.showHidden != true && name.hasPrefix(".") { continue }
106
+ if !query.isEmpty && !name.lowercased().contains(query) { continue }
107
+ if let after = request.afterName, !after.utf8.lexicographicallyPrecedes(name.utf8) { continue }
108
+ var value = stat()
109
+ let available = fstatat(fd, name, &value, AT_SYMLINK_NOFOLLOW) == 0
110
+ if available && request.showHidden != true && value.st_flags & UInt32(UF_HIDDEN) != 0 { continue }
111
+ let row: NativeEntry
112
+ if available && value.st_mode & S_IFMT == S_IFDIR {
113
+ row = NativeEntry(name: name, kind: .directory, identity: nil, selectable: true, reason: nil)
114
+ } else if available && regular(value) && supported(name) && (1...HelperLimits.originalBytes).contains(Int(value.st_size)) {
115
+ row = NativeEntry(name: name, kind: .file, identity: try identity(value), selectable: true, reason: nil)
116
+ } else {
117
+ let reason = !available ? "host_access_denied" :
118
+ value.st_mode & S_IFMT != S_IFREG ? "nonregular" :
119
+ value.st_nlink != 1 ? "hardlink" : !supported(name) ? "unsupported" : "size_limit"
120
+ row = NativeEntry(name: name, kind: .disabled, identity: nil, selectable: false, reason: reason)
121
+ }
122
+ entries.append(row)
123
+ entries.sort { $0.name.utf8.lexicographicallyPrecedes($1.name.utf8) }
124
+ if entries.count > 51 { entries.removeLast() }
125
+ }
126
+ let more = entries.count > 50
127
+ if more { entries.removeLast() }
128
+ try emit(.listed(SelectorListed(requestId: request.requestId, entries: entries, nextName: more ? entries.last?.name : nil)))
129
+ }
130
+ private static func snapshot(_ request: SelectorRequest, emit: (HelperFrame) throws -> Void) throws {
131
+ guard let name = request.segments.last, supported(name), let expected = request.expectedIdentity else {
132
+ throw SelectorFailure.changed
133
+ }
134
+ let parent = try traverse(request, components: request.segments.dropLast())
135
+ defer { close(parent) }
136
+ try boundary("leaf.before")
137
+ // NONBLOCK prevents a swapped FIFO from blocking before fstat rejects it.
138
+ let fd = openat(parent, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC)
139
+ guard fd >= 0 else { throw SelectorFailure.changed }
140
+ defer { close(fd) }
141
+ var value = stat()
142
+ guard fstat(fd, &value) == 0, regular(value) else { throw SelectorFailure.changed }
143
+ let before = try identity(value)
144
+ guard before == expected else { throw SelectorFailure.changed }
145
+ try boundary("copy.before")
146
+ var buffer = [UInt8](repeating: 0, count: 65_536)
147
+ var total = 0
148
+ var index = 0
149
+ var hash = SHA256()
150
+ while true {
151
+ let count = read(fd, &buffer, buffer.count)
152
+ guard count >= 0 else { throw SelectorFailure.access }
153
+ if count == 0 { break }
154
+ total += count
155
+ guard total <= HelperLimits.originalBytes else { throw SelectorFailure.limit }
156
+ guard total <= before.size else { throw SelectorFailure.changed }
157
+ let chunk = Data(buffer.prefix(count))
158
+ hash.update(data: chunk)
159
+ try emit(.chunk(SnapshotChunk(requestId: request.requestId, index: index, base64: chunk.base64EncodedString())))
160
+ try boundary("copy.chunk:\(index)")
161
+ index += 1
162
+ }
163
+ guard fstat(fd, &value) == 0, regular(value) else { throw SelectorFailure.changed }
164
+ let after = try identity(value)
165
+ guard before == after, total == before.size, total > 0 else { throw SelectorFailure.changed }
166
+ let digest = hash.finalize().map { String(format: "%02x", $0) }.joined()
167
+ try emit(.snapshotComplete(SnapshotComplete(requestId: request.requestId, before: before, after: after, bytes: total, sha256: digest)))
168
+ }
169
+ private static func boundary(_ stage: String) throws {
170
+ #if HOST_SELECTOR_TESTING
171
+ let env = ProcessInfo.processInfo.environment
172
+ guard env["HOST_SELECTOR_TEST_STAGE"] == stage, let path = env["HOST_SELECTOR_TEST_BARRIER"] else { return }
173
+ var address = sockaddr_un()
174
+ address.sun_family = sa_family_t(AF_UNIX)
175
+ address.sun_len = UInt8(MemoryLayout<sockaddr_un>.size)
176
+ let bytes = Array(path.utf8) + [0]
177
+ guard bytes.count <= MemoryLayout.size(ofValue: address.sun_path) else { throw SelectorFailure.limit }
178
+ withUnsafeMutableBytes(of: &address.sun_path) { destination in destination.copyBytes(from: bytes) }
179
+ let socketFD = socket(AF_UNIX, SOCK_STREAM, 0)
180
+ guard socketFD >= 0 else { throw SelectorFailure.access }
181
+ defer { close(socketFD) }
182
+ let connected = withUnsafePointer(to: &address) {
183
+ $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { connect(socketFD, $0, socklen_t(MemoryLayout<sockaddr_un>.size)) }
184
+ }
185
+ guard connected == 0 else { throw SelectorFailure.access }
186
+ let message = Array((stage + "\n").utf8)
187
+ guard write(socketFD, message, message.count) == message.count else { throw SelectorFailure.access }
188
+ var ack = [UInt8](repeating: 0, count: 3)
189
+ guard read(socketFD, &ack, ack.count) > 0 else { throw SelectorFailure.access }
190
+ #endif
191
+ }
192
+ }